当前位置: 首页 > news >正文

Twine/Harlowe 网页对话式作品开发技术手册

基于项目实战总结的完整开发指南


📚 目录

  1. 项目基础配置
  2. 音频系统 (HAL)
  3. 视频背景
  4. 字体系统
  5. 鼠标交互
  6. 打字机效果
  7. 变量与数据管理
  8. 分支与随机系统
  9. 定时器与延时显示
  10. 用户输入系统
  11. CSS样式与动画
  12. 图片显示技巧
  13. 标签系统与场景切换
  14. 对话框系统
  15. 条件逻辑与结局判定
  16. 高级技巧

1. 项目基础配置

1.1 StoryData 配置

:: StoryData
{"ifid": "唯一标识符","format": "Harlowe","format-version": "3.3.9","start": "起始段落名","tag-colors": {"office": "red","lab": "orange"},"zoom": 1
}

关键参数说明:

  • ifid: 故事的唯一标识符
  • start: 游戏开始的第一个段落
  • tag-colors: 为不同标签设置颜色,便于编辑器中识别
  • zoom: 编辑器缩放级别

2. 音频系统 (HAL)

2.1 音频文件定义

hal.tracks 特殊段落中定义:

:: hal.tracks
backgroundMusic: audio/background.mp3
hoverSound: audio/hover.mp3
clickSound: audio/click.mp3
goodEndingMusic: audio/good_ending.mp3
badEndingMusic: audio/bad_ending.mp3

支持格式: MP3, OGG, WAV, M4A, AAC, WEBM

2.2 音频播放控制

<!-- 循环播放背景音乐 -->
(track: "backgroundMusic", "loop", true)
(track: "backgroundMusic", "play")<!-- 停止音频 -->
(track: "backgroundMusic", "stop")<!-- 暂停音频 -->
(track: "backgroundMusic", "pause")<!-- 音频音量控制 (0-1) -->
(track: "backgroundMusic", "volume", 0.5)<!-- 淡入效果 (秒数) -->
(track: "backgroundMusic", "fadeIn", 2)<!-- 淡出效果 (秒数) -->
(track: "backgroundMusic", "fadeOut", 2)<!-- 淡入到指定音量 (秒数, 音量) -->
(track: "backgroundMusic", "fadeTo", 2, 0.5)

2.3 鼠标悬停/点击音效

StoryJavaScript 段落中添加:

:: StoryJavaScript [script]
/* 鼠标悬停音效 */
$(document).on('mouseenter', 'tw-link', function() {if(window.A) {window.A.track('hoverSound').play();}
});/* 鼠标点击音效 */
$(document).on('mousedown', 'tw-link', function() {if(window.A) {window.A.track('clickSound').play();}
});

2.4 HAL 完整集成代码

HAL (Harlowe Audio Library) 是一个强大的音频库,需要在 StoryScript 段落中引入完整代码。

核心功能:

  • ✅ 音频预加载
  • ✅ 音量控制面板
  • ✅ 静音功能
  • ✅ 播放列表支持
  • ✅ 音频分组管理
  • ✅ 用户偏好保存 (localStorage)

3. 视频背景

3.1 HTML视频元素

在段落中添加:

<video autoplay muted loop id="background-video"><source src="video/background.mp4" type="video/mp4"><source src="video/background.webm" type="video/webm">您的浏览器不支持视频播放。
</video>

属性说明:

  • autoplay: 自动播放
  • muted: 静音(必须,否则浏览器可能阻止自动播放)
  • loop: 循环播放

3.2 CSS样式配置

:: StoryStylesheet [stylesheet]
#background-video {position: fixed;right: 0;bottom: 0;min-width: 100%; min-height: 100%;width: auto;height: auto;z-index: -100;background-size: cover;object-fit: cover; /* 确保视频填充整个容器 */
}

3.3 确保文字可读性

tw-passage {background-color: rgba(0, 0, 0, 0.5); /* 半透明背景 */padding: 2em;border-radius: 10px;
}

4. 字体系统

4.1 本地字体

@font-face {font-family: 'MyCustomFont';src: url('fonts/IBMPlexMono-Regular.ttf');font-weight: normal;font-style: normal;
}tw-story {font-family: 'MyCustomFont', monospace;
}

4.2 在线字体 (Google Fonts)

@import url('https://fonts.googleapis.com/css2?family=ZCOOL+XiaoWei&display=swap');.text {font-family: 'ZCOOL XiaoWei', serif;
}

4.3 字体变体

/* 粗体 */
font-family: 'IBMPlexMono-Bold', monospace;/* 斜体 */
font-family: 'IBMPlexMono-Italic', monospace;/* 细体 */
font-family: 'IBMPlexMono-Light', monospace;

5. 鼠标交互

5.1 自定义光标

html, body {cursor: url('images/cursor.png'), auto;
}/* 链接悬停时的光标 */
tw-link:hover {cursor: url('images/cursor-hover.png'), pointer;
}

光标图片要求:

  • 推荐格式:PNG(支持透明)
  • 推荐尺寸:32x32 像素
  • 热点位置:(0,0) 左上角

5.2 链接悬停效果

tw-link, .enchantment-link {color: #aaeaff;transition: color 0.3s, text-shadow 0.3s;
}tw-link:hover, .enchantment-link:hover {color: #ffffff;text-shadow: 0 0 8px #ffffff;
}

5.3 自定义悬停样式

.clickText:hover {color: pink;cursor: pointer;
}

6. 打字机效果

6.1 Harlowe原生实现(推荐)

(set: $t1 to "要显示的文本内容"){(set: $p1 to 1) |o1>[](live: 30ms)[(append: ?o1)[(print: $t1's $p1)](set: $p1 to it + 1)(if: $p1 is $t1's length + 1)[(stop:)]]
}

参数说明:

  • 30ms: 每个字符显示间隔(数值越小速度越快)
  • $t1: 存储文本的变量
  • $p1: 当前字符位置

6.2 多段文本打字机

(set: $t1 to "第一段文本")
(set: $t2 to "第二段文本")(live:1s)[{(set: $p1 to 1) |o1>[](live: 30ms)[(append: ?o1)[(print: $t1's $p1)](set: $p1 to it + 1)(if: $p1 is $t1's length + 1)[(stop:)]](stop:)}](live:3s)[{(set: $p2 to 1) |o2>[](live: 30ms)[(append: ?o2)[(print: $t2's $p2)](set: $p2 to it + 1)(if: $p2 is $t2's length + 1)[(stop:)]](stop:)}](live:5s)[[[继续|下一段落]]]

6.3 JavaScript实现(setup函数)

:: StoryJavaScript [script]
setup.typewriter = function(text, targetId, speed) {let i = 0;const elem = $(targetId);const timer = setInterval(function() {if (i < text.length) {elem.append(text.charAt(i));i++;} else {clearInterval(timer);}}, speed);
};

调用方式:

<div id="decision-text"></div>
<script>setup.typewriter("你的文本内容", "#decision-text", 40);
</script>

7. 变量与数据管理

7.1 基础变量

<!-- 设置变量 -->
(set: $playerName to "玩家")
(set: $confidence to 100)
(set: $money to 1000000)<!-- 修改变量 -->
(set: $confidence to $confidence - 10)
(set: $money to $money + 5000)<!-- 布尔值 -->
(set: $hasKey to true)
(set: $isDead to false)

7.2 数组变量

<!-- 创建数组 -->
(set: $items to (a: "Item1", "Item2", "Item3"))<!-- 添加元素 -->
(set: $items to $items + (a: "NewItem"))<!-- 移除元素 -->
(set: $items to $items - (a: "Item1"))<!-- 检查是否包含 -->
(if: (array: ...$items) contains "Item1")[包含该物品
]<!-- 遍历数组 -->
(for: each _item, ...$items)[<span>(print: _item)</span>
]<!-- 数组长度 -->
(print: $items's length)

7.3 临时变量

<!-- 使用下划线开头 -->
(set: _tempValue to 100)
(for: each _item, ...$items)[<!-- _item 是临时变量,只在循环内有效 -->
]

7.4 复杂对象

<!-- 使用多个变量模拟对象 -->
(set: $player_name to "张三")
(set: $player_health to 100)
(set: $player_level to 5)

8. 分支与随机系统

8.1 随机选择 - either

<!-- 随机选择一个选项 -->
(set: $randomChoice to (either: "选项1", "选项2", "选项3"))<!-- 随机跳转 -->
(go-to: (either: "段落1", "段落2", "段落3"))

8.2 洗牌选择 - shuffled

<!-- 从洗牌后的数组中取第一个 -->
(set: $nextEvent to (shuffled: ...$events)'s 1st)<!-- 从数组中移除已选项 -->
(set: $events to $events - (a: $nextEvent))

8.3 数组重置

<!-- 当数组为空时重置 -->
(if: $events's length is 0)[(set: $events to (a: "事件1", "事件2", "事件3"))
]

8.4 随机数生成

<!-- 生成1-100的随机数 -->
(set: $randomNum to (random: 1, 100))<!-- 基于概率的分支 -->
(if: (random: 1, 4) is 1)[<!-- 25%概率触发 -->[[特殊事件]]
](else:)[[[普通事件]]
]

8.5 倒计时随机选择

(set: $counter to 15)
You have |amount>[$counter] seconds left!(live: 1s)[(set: $counter to it - 1)(if: $counter is 0)[(go-to: (either: "选择A", "选择B"))](replace: ?amount)[$counter]
]

9. 定时器与延时显示

9.1 延时显示内容

<!-- 1秒后显示 -->
(live: 1s)[(stop:)显示的内容
]<!-- 3秒后显示链接 -->
(live: 3s)[(stop:)[[继续游戏|下一关]]
]

9.2 多段延时显示

(live: 1s)[第一段文字(stop:)]
(live: 3s)[第二段文字(stop:)]
(live: 5s)[[[继续|下一段落]](stop:)]

9.3 倒计时系统

(set: $counter to 30)
<p>You have (css: "color: red;")[|amount>[$counter]] seconds left!</p>(live: 1s)[(set: $counter to it - 1)(if: $counter is 0)[(go-to: "超时段落")](replace: ?amount)[$counter]
]

9.4 停止其他timer

<!-- 在某个live块中停止自己 -->
(live: 5s)[内容显示(stop:)  <!-- 这会停止这个live块 -->
]

10. 用户输入系统

10.1 文本输入

请输入你的名字:
(input: bind $playerName, "默认值")[[确认|下一段落]]

实际应用:

:: 输入名字
请输入你的名字:
(input: bind $tempName, "")(link: "确认")[(if: $tempName is not "")[(set: $playerName to $tempName)(go-to: "游戏开始")](else:)[(replace: ?error)[请输入名字!]]
]|error>[]

10.2 单选按钮

<form><label><input type="radio" name="gender" value="male" checked> 男性</label><label><input type="radio" name="gender" value="female"> 女性</label>
</form>

10.3 循环选择器

:: 循环选择
(set: $choices to (array: "选项1", "选项2", "选项3"))当前选择: [(display: "Cycling")]<choice|:: Cycling
(link-repeat: (text: $choices's 1st))[(set: $choices to (rotated: -1, ...$choices))(replace: ?choice)[(display: "Cycling")]
]

工作原理:

  • (rotated: -1, ...): 向左旋转数组
  • link-repeat: 可重复点击的链接
  • (replace: ?choice): 替换指定标记的内容

11. CSS样式与动画

11.1 淡入动画

.fade-in {animation: fadeInAnimation 4s ease-in forwards;opacity: 0;
}@keyframes fadeInAnimation {from { opacity: 0; }to { opacity: 1; }
}

11.2 抖动效果

.shake {animation: shake 0.5s infinite;
}@keyframes shake {0%, 100% { transform: translateX(0); }25% { transform: translateX(-10px); }75% { transform: translateX(10px); }
}

11.3 故障效果 (Glitch)

.glitch {animation: glitch 3s infinite;
}@keyframes glitch {0% {text-shadow: 0.05em 0 0 #ff0000, -0.05em -0.025em 0 #00ffff;}14% {text-shadow: 0.05em 0 0 #ff0000, -0.05em -0.025em 0 #00ffff;}15% {text-shadow: -0.05em -0.025em 0 #ff0000, 0.025em 0.025em 0 #00ffff;}/* ... 更多帧 */
}

11.4 模糊效果 (用于神经错乱等效果)

tw-story[tags~="neuroepisode"] {animation: neuroepisode 5s ease;
}@keyframes neuroepisode {0% { filter: blur(0); }25% { filter: blur(5px); }50% { filter: blur(0); }75% { filter: blur(3px); }100% { filter: blur(0); }
}

11.5 渐显内容

.hidden-content {opacity: 0;animation: fadeIn 1s forwards;animation-delay: 2s;
}@keyframes fadeIn {to { opacity: 1; }
}

12. 图片显示技巧

12.1 基础图片

<img src="http://example.com/image.jpg" />

12.2 带CSS类的图片

<img class="centered" src="图片URL" />
<img class="draw" src="图片URL" />
<img class="header-image centered70" src="图片URL" />

CSS定义:

.centered {display: block;margin: 0 auto;
}.draw {display: block;width: 600px;height: auto;margin-left: auto;margin-right: auto;
}.header-image {width: 100%;max-width: 800px;
}.centered70 {width: 70%;margin: 0 auto;
}

12.3 图片定位

<img src="URL" style="float:left; margin-right:12px; width:300px; height:1200;">

13. 标签系统与场景切换

13.1 为段落添加标签

:: 段落名 [office lab] {"position":"100,100","size":"100,100"}
段落内容

13.2 基于标签切换背景

tw-story[tags~="office"] {background-image: url("images/office-bg.jpg");background-size: cover;
}tw-story[tags~="lab"] {background-image: url("images/lab-bg.jpg");background-size: cover;
}tw-story[tags~="forest"] {background-image: url("images/forest-bg.jpg");background-size: cover;
}

优势:

  • 自动切换背景
  • 无需在每个段落中写代码
  • 便于管理大量场景

14. 对话框系统

14.1 基础对话框

<div class="dialog"><span class="character-name">角色名:</span> <span class="typewriter">对话内容</span>
</div>

14.2 对话框样式

.dialog {background-color: rgba(0, 0, 0, 0.7);padding: 1em;margin: 0.5em 0;border-radius: 5px;
}.character-name {color: #4ed6ff;font-weight: bold;text-shadow: 0 0 5px #4ed6ff;
}.typewriter {color: #ffffff;
}

14.3 不同角色的对话样式

.chenduo {color: #ff9999;
}.linxia {color: #99ff99;
}.player {color: #9999ff;
}

使用示例:

<p class="chenduo"><span class="emotion">😊</span> 陈岩说:"这是个好主意!"
</p>

14.4 内心独白

.inner-thought {font-style: italic;color: #a0e0ff;border-left: 3px solid #48baec;padding-left: 1em;
}

15. 条件逻辑与结局判定

15.1 基础条件

(if: $confidence >= 70)[高信赖度内容
](else-if: $confidence >= 40)[中等信赖度内容
](else:)[低信赖度内容
]

15.2 多条件判断

(if: $AIAlignmentIndex >= 100 and $Money > 0)[(go-to: "完美结局")
](else-if: $AIAlignmentIndex >= 100 and $Money < 0)[(go-to: "成功但破产结局")
](else-if: $confidence <= 0)[(go-to: "失败结局")
]

15.3 物品检查

(if: (array: ...$items) contains "钥匙")[你有钥匙,可以开门
](else:)[门被锁住了
]

15.4 历史记录检查

<!-- 检查是否访问过某个段落 -->
(if: (history:) contains "特定段落")[你之前来过这里
]<!-- 检查上一个段落 -->
(if: (history:)'s last is "上个段落")[你是从那里来的
]

15.5 自动结局判定

<!-- 在每个段落结束时检查 -->
(if: $health <= 0)[(go-to: "死亡结局")
](else-if: $score >= 100)[(go-to: "胜利结局")
](else:)[[[继续游戏|下一关]]
]

16. 高级技巧

16.1 进度条显示

HTML结构:

<div class="intimacy-container"><span class="label">亲密度:</span><div class="progress-background"><div class="progress-bar" style="width: 30%;"></div></div><span class="percentage">30%</span>
</div>

CSS样式:

.progress-background {width: 100%;height: 20px;background-color: rgba(255, 255, 255, 0.2);border-radius: 10px;overflow: hidden;
}.progress-bar {height: 100%;background: linear-gradient(90deg, #ff6b9d 0%, #c06c84 100%);transition: width 0.5s ease;
}

16.2 状态栏显示

<div class="stats-bar">开心度: 8/10 | 精力: 10/10 | 羁绊: 5/10
</div>

CSS定位:

.stats-bar {position: absolute;top: -40px;left: 20px;color: white;font-family: 'ZCOOL XiaoWei', serif;font-size: 12px;
}

16.3 动态变量显示

(text-colour:black)+(bg:white)[计算积分 - (text-style:"bold")[$Money] 点声誉 - (text-style:"bold")[$Reputation%]压力水平 - (text-colour:red)+(text-style:"bold")[$PersonalHealth%]
]

16.4 链接样式修饰

<!-- 带对话框提示的链接 -->
(link-repeat: "考虑选项A")[(dialog: "思考分析", "这是详细分析内容")(goto: "选项A段落")
]

16.5 自动更新显示

<!-- 使用命名钩子自动更新 -->
当前金钱: |money>[$money]<!-- 在其他地方更新 -->
(set: $money to $money + 100)
(replace: ?money)[$money]

16.6 隐藏/显示侧边栏

<tw-sidebar><tw-icon tabindex="0" class="undo" title="Undo" style="visibility: hidden;"></tw-icon><tw-icon tabindex="0" class="redo" title="Redo" style="visibility: hidden;"></tw-icon>
</tw-sidebar>

16.7 对话框 (dialog)

(link-repeat: "查看提示")[(dialog: "提示标题", "这是提示内容\n可以多行显示")
]

16.8 Link样式控制

<!-- 改变链接颜色 -->
[[<span style="color: orange;">南瓜</span>|选择南瓜]]<!-- 带特殊效果的链接 -->
(link-goto: "继续", "下一段落")
(link-reveal-goto: "确认", "目标段落")[(set: $confirmed to true)
]

16.9 过渡效果

(transition:'dissolve')[溶解显示的内容]
(transition:'pulse')[脉冲效果]
(transition:'slide-right')[从右滑入]<!-- 带延迟的过渡 -->
(transition:"pulse")+(transition-delay:2s)+(transition-time:3s)[内容]

17. JavaScript集成技巧

17.1 Setup命名空间

:: StoryJavaScript [script]
setup.updateStatus = function() {// 更新状态显示的逻辑
};setup.typewriter = function(text, target, speed) {// 打字机效果实现
};

17.2 段落事件监听

$(document).on(':passagestart', function (ev) {// 每次进入新段落时执行var confidence = State.variables.confidence;if (confidence <= 0) {Engine.play("Bad Ending");}
});

17.3 音频控制

// 在特定条件下播放音频
$(document).on(':passagerender', function (ev) {if (State.variables.isInBattle) {A.track('battleMusic').play();}
});

18. 数据持久化

18.1 localStorage保存

<!-- HAL自动保存音频偏好 -->
<!-- 需要在hal.config中设置 -->
:: hal.config
persistPrefs: true

18.2 Session存储

HAL会自动保存:

  • 音轨列表
  • 播放列表
  • 音频组

18.3 自定义保存

// 保存数据
if (window.localStorage) {localStorage.setItem('saveData', JSON.stringify(State.variables));
}// 读取数据
if (window.localStorage) {var data = JSON.parse(localStorage.getItem('saveData'));State.variables = data;
}

19. 项目中未涉及但常用的技术

19.1 渐变文字

.gradient-text {background: linear-gradient(45deg, #ff6b9d, #c06c84, #6c5b7b);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;
}

19.2 打字光标效果

.typing-cursor::after {content: '|';animation: blink 1s infinite;
}@keyframes blink {0%, 49% { opacity: 1; }50%, 100% { opacity: 0; }
}

19.3 粒子背景效果

需要引入 particles.js 库:

<div id="particles-js"></div><script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<script>particlesJS('particles-js', {particles: {number: { value: 80 },color: { value: '#ffffff' },shape: { type: 'circle' }}});
</script>

19.4 全屏按钮

function toggleFullscreen() {if (!document.fullscreenElement) {document.documentElement.requestFullscreen();} else {document.exitFullscreen();}
}

19.5 保存/加载系统

:: 保存游戏
(set: $saveData to (dm: "money", $money,"reputation", $reputation,"currentPassage", (passage:)'s name
))(if: (save-game: "存档1", $saveData))[游戏已保存!
](else:)[保存失败!
]:: 加载游戏
(if: (saved-games:) contains "存档1")[(load-game: "存档1")
](else:)[没有找到存档
]

19.6 章节跳过功能

:: 章节选择
(link: "跳到第二章")[(set: $chapter to 2)(go-to: "第二章开始")]
(link: "跳到第三章")[(set: $chapter to 3)(go-to: "第三章开始")]

19.7 成就系统

:: 检查成就
(set: $achievements to (a:))(if: $money >= 1000000)[(set: $achievements to $achievements + (a: "百万富翁"))
](if: $reputation >= 90)[(set: $achievements to $achievements + (a: "声名远扬"))
]<!-- 显示成就 -->
已解锁成就:
(for: each _ach, ...$achievements)[🏆 (print: _ach)
]

19.8 背景音乐混合与切换

<!-- 淡出旧音乐,淡入新音乐 -->
(track: "oldMusic", "fadeOut", 2)
(live: 2s)[(stop:)(track: "newMusic", "fadeIn", 2)
]

19.9 屏幕震动效果

@keyframes shake-screen {0%, 100% { transform: translate(0, 0); }10%, 30%, 50%, 70%, 90% { transform: translate(-10px, 0); }20%, 40%, 60%, 80% { transform: translate(10px, 0); }
}.shake-screen {animation: shake-screen 0.5s;
}
// 触发震动
$('tw-story').addClass('shake-screen');
setTimeout(function() {$('tw-story').removeClass('shake-screen');
}, 500);

19.10 文字打字音效

:: 打字音效打字机
(set: $text to "要显示的文本")
{(set: $pos to 1) |output>[](live: 50ms)[(append: ?output)[(print: $text's $pos)](track: "typeSound", "play")  <!-- 每个字符播放音效 -->(set: $pos to it + 1)(if: $pos is $text's length + 1)[(stop:)]]
}

20. 性能优化建议

20.1 音频预加载

:: hal.config
preload: true
loadDelay: 0
trackLoadLimit: 500
totalLoadLimit: 8000

20.2 延迟加载图片

<img src="placeholder.jpg" data-src="actual-image.jpg" class="lazy-load"><script>// 使用Intersection Observer实现懒加载const images = document.querySelectorAll('.lazy-load');const observer = new IntersectionObserver((entries) => {entries.forEach(entry => {if (entry.isIntersecting) {entry.target.src = entry.target.dataset.src;observer.unobserve(entry.target);}});});images.forEach(img => observer.observe(img));
</script>

20.3 减少live宏使用

<!-- 不推荐:多个独立的live -->
(live: 1s)[文本1(stop:)]
(live: 2s)[文本2(stop:)]
(live: 3s)[文本3(stop:)]<!-- 推荐:组合使用 -->
(live: 1s)[文本1(live: 1s)[文本2(live: 1s)[文本3(stop:)](stop:)](stop:)
]

21. 调试技巧

21.1 控制台输出

<!-- 变量调试 -->
(print: $myVariable)<!-- 在浏览器控制台输出 -->
<script>console.log(State.variables);</script>

21.2 HAL调试模式

:: hal.config
debug: true

21.3 显示当前段落名

当前段落: (print: (passage:)'s name)

22. 常见问题与解决方案

22.1 音频无法自动播放

原因: 浏览器政策限制

解决方案:

<!-- 使用playWhenPossible -->
(track: "bgMusic", "playWhenPossible")<!-- 或在用户交互后播放 -->
[[开始游戏|游戏开始]]
<!-- 在"游戏开始"段落中播放音频 -->

22.2 视频在移动设备上不循环

解决方案:

<video autoplay muted loop playsinline webkit-playsinline><source src="video.mp4" type="video/mp4">
</video>

22.3 中文字符显示问题

<head><meta charset="UTF-8">
</head>

22.4 变量未定义错误

<!-- 初始化所有变量 -->
:: 初始化 [startup]
(set: $money to 0)
(set: $items to (a:))
(set: $flags to (dm:))

23. 完整模板示例

23.1 基础游戏模板

:: StoryTitle
我的Twine游戏:: StoryData
{"ifid": "UNIQUE-ID-HERE","format": "Harlowe","format-version": "3.3.9","start": "开始","zoom": 1
}:: hal.tracks
bgMusic: audio/bg.mp3
clickSound: audio/click.mp3
hoverSound: audio/hover.mp3:: StoryStylesheet [stylesheet]
@font-face {font-family: 'CustomFont';src: url('fonts/font.ttf');
}body {cursor: url('images/cursor.png'), auto;background: #000;
}tw-story {font-family: 'CustomFont', sans-serif;color: #fff;
}tw-link:hover {color: #ff69b4;text-shadow: 0 0 10px #ff69b4;
}:: StoryJavaScript [script]
// 鼠标音效
$(document).on('mouseenter', 'tw-link', function() {if(window.A) A.track('hoverSound').play();
});$(document).on('mousedown', 'tw-link', function() {if(window.A) A.track('clickSound').play();
});// 打字机函数
setup.typewriter = function(text, target, speed) {let i = 0;const elem = $(target);const timer = setInterval(function() {if (i < text.length) {elem.append(text.charAt(i));i++;} else {clearInterval(timer);}}, speed);
};:: 开始
(set: $playerName to "")
(set: $health to 100)
(set: $items to (a:))(track: "bgMusic", "loop", true)
(track: "bgMusic", "play")<h1>游戏标题</h1>请输入你的名字:
(input: bind $playerName, "冒险者")[[开始冒险|第一章]]:: 第一章
(set: $text to "欢迎来到冒险世界," + $playerName + "!"){(set: $pos to 1)|output>[](live: 50ms)[(append: ?output)[(print: $text's $pos)](set: $pos to it + 1)(if: $pos is $text's length + 1)[(stop:)]]
}(live: 3s)[(stop:)[[向北走|森林]][[向南走|城镇]]
]

24. 最佳实践

24.1 文件组织

项目目录/
├── story.twee          # 主故事文件
├── audio/             # 音频文件
│   ├── bg.mp3
│   ├── click.mp3
│   └── hover.mp3
├── video/             # 视频文件
│   └── background.mp4
├── fonts/             # 字体文件
│   └── custom.ttf
└── images/            # 图片文件├── cursor.png└── backgrounds/

24.2 命名规范

  • 段落名: 使用清晰描述性名称,如 第一章_森林入口
  • 变量名: 使用驼峰命名,如 $playerName, $currentHealth
  • 临时变量: 使用下划线开头,如 _tempValue
  • 数组: 使用复数形式,如 $items, $achievements

24.3 代码可读性

<!-- 推荐:清晰的缩进和注释 -->
(if: $health > 0)[<!-- 玩家存活 -->(if: $hasWeapon)[你可以战斗](else:)[你需要寻找武器]
](else:)[<!-- 玩家死亡 -->(go-to: "游戏结束")
]

24.4 性能优化

  1. 避免过度使用live宏
  2. 合理使用图片压缩
  3. 音频文件使用适当比特率
  4. 减少不必要的CSS动画

25. 资源引用

25.1 在线资源CDN

<!-- Google Fonts -->
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap');<!-- jQuery (Harlowe已包含) -->
<!-- Particles.js -->
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>

25.2 本地资源

/* 相对路径 */
background-image: url('images/bg.jpg');
src: url('fonts/font.ttf');/* 绝对路径 */
background-image: url('/assets/images/bg.jpg');

26. 响应式设计

26.1 媒体查询

/* 桌面端 */
@media screen and (min-width: 1024px) {tw-passage {width: 60%;font-size: 1.2em;}
}/* 平板 */
@media screen and (max-width: 1023px) and (min-width: 768px) {tw-passage {width: 80%;font-size: 1em;}
}/* 移动端 */
@media screen and (max-width: 767px) {tw-passage {width: 95%;font-size: 0.9em;}.stats-bar {font-size: 10px;}
}

27. 特殊效果实现

27.1 文字逐渐显现(reveal)

(link-reveal: "点击查看隐藏内容")[这是隐藏的内容
]

27.2 替换文本

原始文本 (link-replace: "点击替换")[替换后的文本]

27.3 追加文本

|hook>[](link: "添加内容")[(append: ?hook)[新增的内容]
]

27.4 倒计时加载屏幕

(set: $countdown to 5)
Loading... |time>[$countdown](live: 1s)[(set: $countdown to it - 1)(replace: ?time)[$countdown](if: $countdown is 0)[(stop:)(go-to: "游戏主菜单")]
]

28. 错误处理

28.1 音频加载失败

$(document).on(':available', function(ev) {console.log('音频可用:', ev.track.id);
});$(document).on(':loaded', function(ev) {console.log('音频加载完成:', ev.track.id);
});

28.2 变量检查

(if: $myVar is not undefined)[变量已定义
](else:)[变量未定义,使用默认值(set: $myVarto 0)
]

29. 高级音频技术

29.1 播放列表

<!-- 创建播放列表 -->
(newplaylist: "myPlaylist", "track1", "track2", "track3")<!-- 播放播放列表 -->
(playlist: "myPlaylist", "play")<!-- 洗牌播放 -->
(playlist: "myPlaylist", "shuffle")
(playlist: "myPlaylist", "play")<!-- 停止播放列表 -->
(playlist: "myPlaylist", "stop")

29.2 音频分组

<!-- 创建音频组 -->
(newgroup: "sfxGroup", "click", "hover", "whoosh")<!-- 控制整组音频 -->
(group: "sfxGroup", "volume", 0.3)
(group: "sfxGroup", "mute", true)

29.3 音频事件监听

// 监听音频播放事件
A.on('play', function(ev) {console.log('正在播放:', ev.track.id);
});// 监听音频停止事件
A.on('stop', function(ev) {console.log('已停止:', ev.track.id);
});

30. 实战技巧汇总

30.1 状态更新系统

:: StoryJavaScript [script]
setup.updateStatus = function() {// 更新所有状态显示$('#money-display').text('$' + State.variables.money);$('#reputation-display').text(State.variables.reputation + '%');
};
<!-- 在段落中调用 -->
(set: $money to $money + 1000)
<script>setup.updateStatus();</script>

30.2 对话框预设样式

<!-- 使用一致的对话框结构 -->
<div class="textbox" style="width: 720px;"><img src="对话框背景.jpg" style="width: 100%; height: auto;"><div class="stats-bar">开心度: 8/10 | 精力: 10/10</div><div class="text">对话文本内容</div>
</div>

30.3 场景描述样式

<div class="scene-description">环境描述文字
</div><div class="scene-transition">场景转换提示
</div>

30.4 结局提示样式

<div class="outcome"><p><span class="emotion"></span> 成功提示</p>
</div><div class="outcome fail"><p><span class="emotion"></span> 失败提示</p>
</div>

31. 特效代码片段库

31.1 文字颜色动态变化

.color-shift {animation: colorShift 3s infinite;
}@keyframes colorShift {0% { color: #ff6b9d; }33% { color: #c06c84; }66% { color: #6c5b7b; }100% { color: #ff6b9d; }
}

31.2 脉冲发光效果

.pulse-glow {animation: pulseGlow 2s ease-in-out infinite;
}@keyframes pulseGlow {0%, 100% {text-shadow: 0 0 5px #4ed6ff;}50% {text-shadow: 0 0 20px #4ed6ff, 0 0 30px #4ed6ff;}
}

31.3 悬浮动画

.float {animation: float 3s ease-in-out infinite;
}@keyframes float {0%, 100% { transform: translateY(0px); }50% { transform: translateY(-20px); }
}

31.4 旋转加载动画

.spinner {display: inline-block;width: 40px;height: 40px;border: 4px solid rgba(255,255,255,0.3);border-radius: 50%;border-top-color: #fff;animation: spin 1s linear infinite;
}@keyframes spin {to { transform: rotate(360deg); }
}

32. 项目文件结构建议

MyTwineProject/
├── story.twee                 # 主故事文件
├── 素材/
│   ├── 音乐/
│   │   ├── BGM.mp3           # 背景音乐
│   │   ├── 点击.MP3          # 点击音效
│   │   └── 光标掠过选项.MP3   # 悬停音效
│   ├── 视频/
│   │   └── Background.mp4    # 背景视频
│   ├── 字体/
│   │   └── Custom.ttf        # 自定义字体
│   └── 图片/
│       ├── 光标.png          # 自定义光标
│       └── 场景/             # 场景图片
└── README.md                 # 项目说明

33. 常用Harlowe宏速查

33.1 文本样式

(text-colour: red)[红色文字]
(text-style: "bold")[粗体]
(text-style: "italic")[斜体]
(text-style: "underline")[下划线]
(text-style: "double-underline")[双下划线]
(text-style: "rumble")[震动文字]
(text-size: 1.5)[放大1.5倍]
(css: "font-size: 150%;")[自定义CSS]

33.2 对齐

(align: "=><=")[居中]
(align: "==><==")[完全居中]
(align: "<==")[右对齐]

33.3 背景色

(bg: white)[白色背景]
(bg: (rgb: 255, 0, 0))[RGB背景]

34. 调试命令

34.1 显示所有变量

(print: (datanames:))
(print: (datavalues:))

34.2 查看历史

访问过的段落:(print: (history:))
上一个段落:(print: (history:)'s last)

34.3 强制跳转

<!-- 无条件跳转 -->
(go-to: "目标段落")<!-- 根据变量跳转 -->
(goto: $targetPassage)

35. 移动端优化

35.1 触摸友好设计

/* 增大可点击区域 */
tw-link {padding: 0.5em 1em;display: inline-block;min-height: 44px; /* iOS推荐最小触摸区域 */
}

35.2 禁用文字选择

body {-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;
}

35.3 视口设置

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

36. 高级变量技巧

36.1 DataMap (数据映射)

<!-- 创建数据映射 -->
(set: $player to (dm: "name", "张三","health", 100,"items", (a: "剑", "shield")
))<!-- 访问值 -->
(print: $player's name)
(print: $player's health)<!-- 修改值 -->
(set: $player's health to 80)

36.2 DataSet (数据集)

<!-- 创建集合(无重复元素) -->
(set: $achievements to (ds: "新手", "探索者"))<!-- 添加元素 -->
(set: $achievements to $achievements + (ds: "勇士"))<!-- 检查是否包含 -->
(if: $achievements contains "新手")[你是新手
]

37. 完整示例:对话系统

:: 对话场景
(set: $dialogues to (a:"你好,欢迎来到这里。","我是这里的守护者。","你有什么问题吗?"
))(set: $currentDialog to 0)<div class="dialog-box"><div class="character-portrait"><img src="npc.jpg"></div><div class="character-name">守护者</div><div class="dialog-text">|dialog>[(print: $dialogues's ($currentDialog + 1))]</div>
</div>(if: $currentDialog < $dialogues's length - 1)[(link: "继续 →")[(set: $currentDialog to it + 1)(replace: ?dialog)[(print: $dialogues's ($currentDialog + 1))]]
](else:)[[[对话结束|下一场景]]
]

38. 发布前检查清单

  • 所有音频文件路径正确
  • 字体文件已包含
  • 视频文件大小合理(推荐<50MB)
  • 图片已压缩优化
  • 在不同浏览器测试(Chrome, Firefox, Safari)
  • 移动端测试
  • 检查所有链接是否正确
  • 变量初始化完整
  • 移除调试代码
  • 拼写检查

39. 关键性能指标

39.1 推荐限制

  • 音频文件: 单个 < 10MB,总计 < 50MB
  • 视频文件: < 50MB (考虑压缩)
  • 图片文件: 单个 < 500KB
  • 字体文件: 单个 < 2MB
  • 总项目大小: < 100MB

39.2 加载优化

:: hal.config
preload: true          # 预加载音频
loadDelay: 0          # 加载延迟(毫秒)
trackLoadLimit: 500   # 单个音轨加载超时(毫秒)
totalLoadLimit: 8000  # 总加载超时(毫秒)

40. 推荐工具与资源

40.1 开发工具

  • Twine 2: https://twinery.org/
  • Tweego: 命令行编译工具
  • VS Code: 配合twee3语言服务器

40.2 资源网站

  • 免费音效: Freesound.org, Pixabay
  • 免费音乐: YouTube Audio Library
  • 免费字体: Google Fonts, Font Squirrel
  • 图片: Unsplash, Pexels

40.3 社区资源

  • Twine Wiki: https://twinery.org/wiki/
  • Harlowe手册: https://twine2.neocities.org/
  • HAL文档: Chapel’s Audio Library

附录A:完整的HAL配置示例

:: hal.config
preload: true
loadDelay: 0
muteOnBlur: true
startingVol: 0.5
persistPrefs: true
globalA: true
showControls: true
sidebarStartClosed: true
volumeDisplay: false
trackLoadLimit: 500
totalLoadLimit: 8000
debug: false

参数说明:

  • preload: 预加载所有音频
  • muteOnBlur: 窗口失焦时静音
  • startingVol: 初始音量 (0-1)
  • persistPrefs: 保存用户偏好
  • globalA: 创建全局A对象
  • showControls: 显示音量控制面板
  • volumeDisplay: 显示音量数值

附录B:完整的项目模板

详见第23节的完整模板示例。


附录C:快捷键参考

在Twine编辑器中:

  • Ctrl/Cmd + S: 保存
  • Ctrl/Cmd + F: 查找
  • Ctrl/Cmd + Z: 撤销
  • Ctrl/Cmd + Shift + Z: 重做

📖 总结

本手册基于实际项目开发经验总结,涵盖了Twine/Harlowe开发的所有核心技术:

音频系统: HAL库的完整应用
视觉效果: 视频背景、图片、动画
交互设计: 鼠标、键盘、用户输入
文本效果: 打字机、渐显、样式
逻辑控制: 变量、条件、随机、定时器
性能优化: 资源管理、加载策略
高级技巧: 数据持久化、事件系统


🎯 快速开发流程

  1. 规划结构 → 设计故事分支图
  2. 配置基础 → StoryData + hal.tracks
  3. 准备资源 → 音频、视频、图片、字体
  4. 编写样式 → StoryStylesheet
  5. 实现逻辑 → 变量系统 + 分支逻辑
  6. 添加特效 → 打字机 + 动画
  7. 测试优化 → 多浏览器测试
  8. 发布 → 导出HTML文件

文档版本: v1.0
最后更新: 2025-01-10
基于: 实际项目文件分析
适用: Harlowe 3.3.9+


相关文件参考

本项目中的实际应用示例:

  • MapIsUnavailable.twee: HAL音频系统完整实现
  • _____5.twee: 打字机效果与状态栏
  • [2060_ London Hive Crisis.twee](2060_ London Hive Crisis.twee): 标签系统与场景切换
  • [AI Alignment crisis_ Novaco_.twee](AI Alignment crisis_ Novaco_.twee): 变量管理与条件判断
  • ____Revelation_.twee: 音频控制与用户输入

提示: 所有代码示例均已在实际项目中验证有效。建议先从简单功能开始,逐步添加复杂特性。

http://www.dtcms.com/a/599228.html

相关文章:

  • 怎样让自己的网站被收录博客网站是自己做的吗
  • 用jsp做网站的技术路线英文网站建站山东
  • 店铺装修网站免费建立网站软件
  • php网站出现乱码app推广拉新一手渠道
  • 电气工程师求职问答-中级篇
  • 个人博客网站备案吗最简单的网站怎么做
  • 织梦更新网站地图画网站 模板
  • 个人网站 做外贸wordpress安卓显示
  • 峰值保持电路与峰值提取算法实现放射性核脉冲信号峰值提取
  • 内网网站搭建设推销什么企业做网站和app
  • 开源展示型网站莱芜百度贴吧
  • 做网站要注意的wordpress找回密碼
  • 东莞网站建设 光龙做网站备案哪些条件
  • 维护网站建设空间出租怎么生成链接
  • 搜狗推广做网站要钱吗手机网站怎么搜索引擎
  • 第5天python内容
  • 网站建设进展报告做excel的网站
  • 怎样做付费下载的网站wordpress 引用 插件
  • 连云港市建设银行网站wordpress排除首页显示
  • 内江市规划建设教育培训中心网站wordpress 微信公众
  • 国外的贸易网站智能营销系统
  • android 线程loop
  • 自己的网站如何给别人做有偿广告网站开发的目的意义
  • 代码随想录 134.加油站
  • 专门做水产海鲜的网站吗网站添加视频
  • 电子电气架构 --- 整车EEA简述
  • 双辽建设局网站手工制作房子
  • 网站服务器慢福田在线官网
  • 54.渗透-Yakit-基础模块应用(子域名收集)
  • 化妆品网站程序熟悉网站空间 域名等相关知识