更新 temp.md
This commit is contained in:
440
temp.md
440
temp.md
@@ -1,98 +1,342 @@
|
||||
# 《遗物拾荒者:玩偶生存日记》 - 核心设计方案
|
||||
## 一、 游戏概述
|
||||
### 1.1 核心定位
|
||||
- **类型**:3D低多边形风格 大地图探索 + Roguelite 幸存者割草
|
||||
- **平台**:微信小游戏
|
||||
- **视角**:上帝俯视视角(固定45度)
|
||||
- **单局时长**:10-15分钟(单次建筑探索)
|
||||
- **核心卖点**:悬疑治愈的废土童话 + 爽快割草收集 + 丰富的藏身处经营与装备构筑
|
||||
### 1.2 背景故事
|
||||
人类在一夜之间离奇消失,世界陷入死寂。然而,人类留下的生活物件却意外苏醒了灵魂。大部分物件变成了凭借本能游荡、极具攻击性的“狂化杂物”;而极少数曾被人类寄予深厚感情的物件,则诞生了自我意识与思想。
|
||||
玩家扮演一个破旧但被主人珍爱的毛绒小熊玩偶。你在空无一人的公寓中醒来,立刻遭到了狂化电器的追杀。在新手教程中,你一路突围,最终逃入了一个废弃的地下地铁维护室,以此作为**“地下藏身处”**。
|
||||
为了生存、寻找其他拥有理智的“幸存者(觉醒物件)”,并揭开人类消失的谜团,你必须离开安全的地下,踏上危机四伏的大地图,探索各种建筑废墟,收集生存物资与灵魂的碎片。
|
||||
## 二、 核心玩法架构
|
||||
### 2.1 游戏主循环(Meta Loop)
|
||||
地下藏身处(休整/升级/制造) ➔ 大地图(选择建筑节点) ➔ 进入建筑(爬塔式幸存者割草) ➔ 收集材料/击败Boss获取灵魂 ➔ 返回藏身处
|
||||
### 2.2 大地图探索(Macro)
|
||||
- **节点式地图**:玩家离开藏身处后进入大地图界面,地图由各个建筑节点组成(如:阳光公寓、废弃便利店、市立图书馆、市立医院等)。
|
||||
- **解锁机制**:通关前置建筑、或在藏身处修理通讯设备截获求救信号,可解锁新的大地图区域。
|
||||
### 2.3 局内战斗:爬楼层割草(Micro)
|
||||
- **固定地图爬层**:进入建筑后,切换为幸存者割草玩法。每个楼层是一个固定大小的封闭房间/区域,清理完本层所有怪物或坚持指定时间后,出现前往下一层的楼梯/电梯。
|
||||
- **操作**:虚拟摇杆移动,自动攻击。
|
||||
- **战斗爽感**:强调击退与破碎感。击败狂化物件时会有零件散落的视觉反馈。
|
||||
- **灵魂觉醒值**:连续击杀积攒“觉醒槽”,满后触发“灵魂觉醒”——主角浑身散发金光,移速大增,全屏怪物受牵引并持续受到真实伤害。
|
||||
## 三、 资源与掉落系统(材料细化)
|
||||
在局内击败不同类型的怪物,会掉落不同类型的材料。这些材料是藏身处养成的核心。
|
||||
### 3.1 零碎材料(击败普通小怪掉落)
|
||||
| 材料名称 | 来源倾向 | 核心用途 |
|
||||
|---|---|---|
|
||||
| 柔软棉絮 | 布偶、衣物类怪物 | 升级基础生存属性(血量、回复速度) |
|
||||
| 废旧塑料 | 日用品、外壳类怪物 | 制造/强化防御性装备或护盾技能 |
|
||||
| 生锈齿轮 | 机械、电器类怪物 | 制造/强化攻击性武器的基础伤害 |
|
||||
| 报废电池 | 遥控器、电子表等 | 强化技能的攻击频率、冷却缩减 |
|
||||
### 3.2 核心资源(击败精英/Boss掉落)
|
||||
| 材料名称 | 来源 | 核心用途 |
|
||||
|---|---|---|
|
||||
| 灵魂碎片 | 关卡Boss、隐藏精英怪 | 最高级货币。用于解锁新角色(幸存者)、解锁关键天赋节点、兑换强力传奇道具。 |
|
||||
| 物件图纸 | 宝箱层、首通奖励 | 在藏身处解锁新的可制造武器或技能。 |
|
||||
## 四、 局外养成:地下藏身处系统
|
||||
藏身处是玩家的大本营,随着游戏推进,可以不断扩建和解锁新设施。
|
||||
### 4.1 设施清单与功能
|
||||
- **【缝纫机】(属性升级)**
|
||||
* 消耗 柔软棉絮 和 废旧塑料,为主角缝补身体。
|
||||
* 提升数值:最大生命值、移动速度、拾取范围、伤害减免。
|
||||
- **【破旧工作台】(武器制造与强化**
|
||||
* 消耗 生锈齿轮、报废电池 和 图纸。
|
||||
* 玩家可以在出战前自由装配/制造初始携带的武器。
|
||||
* 例如:消耗齿轮将“生锈的图钉”升级为“锋利图钉(开局自带穿透效果)”。
|
||||
- **【灵魂陈列架】(天赋与角色解锁)**
|
||||
* 消耗 灵魂碎片。
|
||||
* 解锁强力能力:如“复活一次”、“开局自带一次技能三选一刷新”等。
|
||||
* 解锁新角色:用灵魂碎片唤醒带回来的其他物件。例如解锁“铁皮青蛙(高移速、低血量)”、“发条骑士(自带高护甲)”。
|
||||
### 4.2 特殊NPC互动
|
||||
#### 飞天速递箱(商城系统)
|
||||
- **解锁条件**:第一次从大地图探索归来时,会触发剧情——一个长着螺旋桨的快递纸箱从通风管掉下来。它是一个拥有财迷意识的活包裹。
|
||||
- **功能**:每天随机刷新商品。玩家可以使用多余的零碎材料与它以物易物,或者观看广告/使用灵魂碎片购买稀有图纸、大量材料包。
|
||||
#### 老旧收音机(剧情与任务系统)
|
||||
* 随着游戏进展修复。会不定期接收到来自其他城市的“杂音信号”。
|
||||
* 玩家通过破译信号(达成特定击杀成就或通关特定层数),可以解锁世界观日记碎片,并标记大地图上的隐藏Boss关卡。
|
||||
## 五、 Roguelite系统设计(局内构筑)
|
||||
### 5.1 武器与进化路线(基于新设定)
|
||||
局内升级时,玩家可以从随机提供的能力中三选一。
|
||||
| 基础武器 (日用品) | 攻击方式 | 满级+特定被动进化为【觉醒形态】 |
|
||||
|---|---|---|
|
||||
| 大头针 | 向前直线发射 | 暴雨梨花针:全方位散射,带穿透 |
|
||||
| 悠悠球 | 环绕自身旋转 | 重力星轨:范围极大,附加减速力场 |
|
||||
| 火柴棒 | 随机抛射,小范围爆炸 | 烈焰喷灯:持续扇形喷火,留下燃烧带 |
|
||||
| 回形针链 | 像鞭子一样抽打 | 电磁锁链:攻击在敌人间弹射导电 |
|
||||
### 5.2 被动物品(辅助)
|
||||
- **创可贴**:每秒恢复生命值。
|
||||
- **放大镜**:增加所有武器的攻击范围。
|
||||
- **发条钥匙**:提升移动速度和攻击速度。
|
||||
- **纽扣**:增加护甲,减少受到的伤害。
|
||||
## 六、 商业化方案(微信小游戏生态)
|
||||
全部采用不强迫的激励视频广告,保障游戏沉浸感。
|
||||
### 6.1 飞天速递箱(核心广告点)
|
||||
- **每日空投补给**:观看视频,获取随机大量基础材料(棉絮/齿轮)。
|
||||
- **高级货架刷新**:商店列表无免费次数时,看视频刷新。
|
||||
- **图纸碎片特卖**:偶尔刷出极其稀有的武器图纸碎片,需看视频获取。
|
||||
### 6.2 局内与结算
|
||||
- **濒死急救**:局内死亡时,长着翅膀的速递箱飞来,观看视频可满血复活1次。
|
||||
- **搜刮翻倍**:战斗结算界面,观看视频使本次探索获得的所有“零碎材料”数量翻倍(灵魂碎片不可翻倍,保值)。
|
||||
## 七、 美术与氛围表现
|
||||
### 视觉风格:
|
||||
3D低多边形(Low-Poly)。色调上,大地图和背景采用稍微黯淡的废土色系(灰白、锈红),但拥有灵魂的物体(主角、NPC、重要道具)使用鲜艳明亮的色彩并带有微光,形成强烈的视觉对比。
|
||||
### UI设计:
|
||||
采用“手账本”、“牛皮纸”、“拍立得照片”等元素。比如技能选择界面就像是从手账本上撕下来的便利贴。
|
||||
### 音效:
|
||||
- **探索时**:安静、带有一点点孤独感的钢琴或吉他BGM。
|
||||
- **战斗时**:快节奏的电子乐混合物品碰撞的清脆声(如玻璃碎裂、金属交击)。
|
||||
## 八、 开发规划与迭代预留
|
||||
### 8.1 首测版本(V1.0):
|
||||
* 实装“阳光公寓”单个建筑(无限层模式),跑通战斗、材料掉落、缝纫机升级与工作台基础武器制造。实装快递箱NPC。
|
||||
### 8.2 内容扩展(V1.5):
|
||||
* 实装大地图系统,加入“废弃便利店”、“街道区”。
|
||||
* 加入【灵魂碎片】及多角色解锁系统(增加2个新可控角色)。
|
||||
### 8.3 社交与长线(V2.0+):
|
||||
* 漂流瓶系统:玩家可以在收音机处写下留言放入漂流瓶,随机发送给其他玩家(异步社交),捡到漂流瓶的玩家可获得少量材料。
|
||||
* 深渊地下室:高难度的周常挑战副本,怪物数值极高,检验玩家的终极构筑,产出限定外观(给毛绒熊穿不同的衣服等)。
|
||||
using System.Collections.Generic;
|
||||
using Unity.AI.Navigation;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
public class ProceduralMapGenerator : MonoBehaviour
|
||||
{
|
||||
[Header("生成配置")]
|
||||
public int seed = 12345;
|
||||
public int maxPieces = 20;
|
||||
public float gridSize = 2.0f;
|
||||
|
||||
[Header("预制体库")]
|
||||
public List<GameObject> mapPiecePrefabs = new List<GameObject>();
|
||||
public GameObject wallPiecePrefab; // 封口模块
|
||||
|
||||
[Header("后处理")]
|
||||
public bool enableMeshCombining = true;
|
||||
public bool enableNavMesh = true;
|
||||
|
||||
[Header("调试")]
|
||||
public bool showDebugGizmos = true;
|
||||
|
||||
// 内部状态
|
||||
private Dictionary<Vector3Int, GameObject> placedPieces = new Dictionary<Vector3Int, GameObject>();
|
||||
private List<ConnectionPoint> openConnections = new List<ConnectionPoint>();
|
||||
private System.Random random;
|
||||
private Transform mapRoot;
|
||||
|
||||
void Start()
|
||||
{
|
||||
GenerateMap();
|
||||
}
|
||||
|
||||
[ContextMenu("生成地图")]
|
||||
public void GenerateMap()
|
||||
{
|
||||
ClearMap();
|
||||
InitializeRandom();
|
||||
|
||||
// 创建地图根对象
|
||||
mapRoot = new GameObject("GeneratedMap").transform;
|
||||
mapRoot.SetParent(transform);
|
||||
|
||||
// 选择起始模块
|
||||
PlaceStartPiece();
|
||||
|
||||
// 主生成循环
|
||||
GenerateLoop();
|
||||
|
||||
// 后处理
|
||||
if (enableMeshCombining)
|
||||
CombineMeshes();
|
||||
|
||||
if (enableNavMesh)
|
||||
BuildNavMesh();
|
||||
}
|
||||
|
||||
[ContextMenu("清除地图")]
|
||||
public void ClearMap()
|
||||
{
|
||||
if (mapRoot != null)
|
||||
DestroyImmediate(mapRoot.gameObject);
|
||||
|
||||
placedPieces.Clear();
|
||||
openConnections.Clear();
|
||||
}
|
||||
|
||||
private void InitializeRandom()
|
||||
{
|
||||
random = new System.Random(seed);
|
||||
}
|
||||
|
||||
private void PlaceStartPiece()
|
||||
{
|
||||
// 查找起始模块e
|
||||
GameObject startPrefab = null;
|
||||
foreach (var prefab in mapPiecePrefabs)
|
||||
{
|
||||
var piece = prefab.GetComponent<MapPiece>();
|
||||
if (piece != null && piece.isStartPiece)
|
||||
{
|
||||
startPrefab = prefab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startPrefab == null && mapPiecePrefabs.Count > 0)
|
||||
{
|
||||
startPrefab = mapPiecePrefabs[0];
|
||||
}
|
||||
|
||||
if (startPrefab == null)
|
||||
{
|
||||
Debug.LogError("没有可用的起始模块预制体!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 实例化起始模块
|
||||
var startPiece = Instantiate(startPrefab, Vector3.zero, Quaternion.identity, mapRoot);
|
||||
var mapPiece = startPiece.GetComponent<MapPiece>();
|
||||
|
||||
// 记录位置
|
||||
Vector3Int gridPos = WorldToGrid(Vector3.zero);
|
||||
placedPieces[gridPos] = startPiece;
|
||||
|
||||
// 添加所有连接点到开放列表
|
||||
openConnections.AddRange(mapPiece.GetAvailableConnectionPoints());
|
||||
|
||||
Debug.Log($"放置起始模块: {mapPiece.pieceName} 在位置 {gridPos}");
|
||||
}
|
||||
|
||||
private void GenerateLoop()
|
||||
{
|
||||
int attempts = 0;
|
||||
int maxAttempts = maxPieces * 3; // 最大尝试次数
|
||||
|
||||
while (openConnections.Count > 0 && placedPieces.Count < maxPieces && attempts < maxAttempts)
|
||||
{
|
||||
attempts++;
|
||||
|
||||
// 从开放列表中取出一个连接点
|
||||
var connection = openConnections[0];
|
||||
openConnections.RemoveAt(0);
|
||||
|
||||
// 尝试放置新模块
|
||||
if (TryPlacePieceAtConnection(connection))
|
||||
{
|
||||
Debug.Log($"成功放置第 {placedPieces.Count} 个模块");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 放置失败,放置封口模块
|
||||
PlaceWallPiece(connection);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"生成完成!共放置 {placedPieces.Count} 个模块,尝试次数: {attempts}");
|
||||
}
|
||||
|
||||
private bool TryPlacePieceAtConnection(ConnectionPoint connection)
|
||||
{
|
||||
// 获取连接点的世界位置和方向
|
||||
Vector3 worldPos = connection.localPosition; // 这里需要修正,应该从父对象获取
|
||||
Vector3 worldDir = connection.localDirection;
|
||||
|
||||
// 计算新模块的位置
|
||||
Vector3 newPiecePos = worldPos + worldDir * gridSize;
|
||||
Vector3Int gridPos = WorldToGrid(newPiecePos);
|
||||
|
||||
// 检查位置是否已被占用
|
||||
if (placedPieces.ContainsKey(gridPos))
|
||||
return false;
|
||||
|
||||
// 查找合适的预制体
|
||||
var candidates = FindCandidatePieces(connection);
|
||||
if (candidates.Count == 0)
|
||||
return false;
|
||||
|
||||
// 随机选择一个候选
|
||||
GameObject selectedPrefab = candidates[random.Next(candidates.Count)];
|
||||
|
||||
// 计算旋转(使新模块的连接点与当前连接点对齐)
|
||||
Quaternion rotation = Quaternion.LookRotation(-worldDir, Vector3.up);
|
||||
|
||||
// 实例化新模块
|
||||
var newPiece = Instantiate(selectedPrefab, newPiecePos, rotation, mapRoot);
|
||||
var newMapPiece = newPiece.GetComponent<MapPiece>();
|
||||
|
||||
// 检查碰撞
|
||||
if (CheckCollision(newPiece))
|
||||
{
|
||||
DestroyImmediate(newPiece);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 记录位置
|
||||
placedPieces[gridPos] = newPiece;
|
||||
|
||||
// 添加新模块的连接点到开放列表(除了与当前连接点匹配的那个)
|
||||
var newConnections = newMapPiece.GetAvailableConnectionPoints();
|
||||
foreach (var newConn in newConnections)
|
||||
{
|
||||
// 简单的方向检查,避免添加反向连接
|
||||
Vector3 newWorldDir = newPiece.transform.TransformDirection(newConn.localDirection);
|
||||
if (Vector3.Dot(newWorldDir, worldDir) < -0.5f) // 大致相反方向
|
||||
continue;
|
||||
|
||||
openConnections.Add(newConn);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<GameObject> FindCandidatePieces(ConnectionPoint connection)
|
||||
{
|
||||
var candidates = new List<GameObject>();
|
||||
|
||||
foreach (var prefab in mapPiecePrefabs)
|
||||
{
|
||||
var mapPiece = prefab.GetComponent<MapPiece>();
|
||||
if (mapPiece == null) continue;
|
||||
|
||||
// 简单的风格匹配(可扩展)
|
||||
if (mapPiece.style != connection.style && connection.style != "default")
|
||||
continue;
|
||||
|
||||
candidates.Add(prefab);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private void PlaceWallPiece(ConnectionPoint connection)
|
||||
{
|
||||
if (wallPiecePrefab == null) return;
|
||||
|
||||
Vector3 worldPos = connection.localPosition;
|
||||
Vector3 worldDir = connection.localDirection;
|
||||
Vector3 wallPos = worldPos + worldDir * gridSize * 0.5f;
|
||||
Vector3Int gridPos = WorldToGrid(wallPos);
|
||||
|
||||
if (!placedPieces.ContainsKey(gridPos))
|
||||
{
|
||||
var wallPiece = Instantiate(wallPiecePrefab, wallPos, Quaternion.LookRotation(-worldDir), mapRoot);
|
||||
placedPieces[gridPos] = wallPiece;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckCollision(GameObject piece)
|
||||
{
|
||||
var collider = piece.GetComponent<Collider>();
|
||||
if (collider == null) return false;
|
||||
|
||||
// 简单的重叠检测
|
||||
var bounds = collider.bounds;
|
||||
foreach (var placedPiece in placedPieces.Values)
|
||||
{
|
||||
var placedCollider = placedPiece.GetComponent<Collider>();
|
||||
if (placedCollider != null && placedCollider.bounds.Intersects(bounds))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Vector3Int WorldToGrid(Vector3 worldPos)
|
||||
{
|
||||
return new Vector3Int(
|
||||
Mathf.RoundToInt(worldPos.x / gridSize),
|
||||
Mathf.RoundToInt(worldPos.y / gridSize),
|
||||
Mathf.RoundToInt(worldPos.z / gridSize)
|
||||
);
|
||||
}
|
||||
|
||||
private void CombineMeshes()
|
||||
{
|
||||
// 简单的网格合并实现
|
||||
var meshFilters = mapRoot.GetComponentsInChildren<MeshFilter>();
|
||||
if (meshFilters.Length == 0) return;
|
||||
|
||||
// 按材质分组合并
|
||||
var combineInstances = new List<CombineInstance>();
|
||||
var materials = new List<Material>();
|
||||
|
||||
foreach (var meshFilter in meshFilters)
|
||||
{
|
||||
if (meshFilter.sharedMesh == null) continue;
|
||||
|
||||
var renderer = meshFilter.GetComponent<MeshRenderer>();
|
||||
if (renderer == null || renderer.sharedMaterial == null) continue;
|
||||
|
||||
var combine = new CombineInstance
|
||||
{
|
||||
mesh = meshFilter.sharedMesh,
|
||||
transform = meshFilter.transform.localToWorldMatrix
|
||||
};
|
||||
|
||||
combineInstances.Add(combine);
|
||||
|
||||
// 禁用原始渲染器
|
||||
renderer.enabled = false;
|
||||
}
|
||||
|
||||
if (combineInstances.Count > 0)
|
||||
{
|
||||
// 创建合并的网格对象
|
||||
var combinedObject = new GameObject("CombinedMesh");
|
||||
combinedObject.transform.SetParent(mapRoot);
|
||||
|
||||
var combinedFilter = combinedObject.AddComponent<MeshFilter>();
|
||||
var combinedRenderer = combinedObject.AddComponent<MeshRenderer>();
|
||||
|
||||
// 合并网格
|
||||
var combinedMesh = new Mesh();
|
||||
combinedMesh.CombineMeshes(combineInstances.ToArray());
|
||||
combinedFilter.sharedMesh = combinedMesh;
|
||||
|
||||
// 设置材质(使用第一个材质)
|
||||
if (materials.Count > 0)
|
||||
combinedRenderer.sharedMaterial = materials[0];
|
||||
|
||||
Debug.Log($"网格合并完成,合并了 {combineInstances.Count} 个网格");
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildNavMesh()
|
||||
{
|
||||
var navMeshSurface = mapRoot.gameObject.GetComponent<NavMeshSurface>();
|
||||
if (navMeshSurface == null)
|
||||
navMeshSurface = mapRoot.gameObject.AddComponent<NavMeshSurface>();
|
||||
|
||||
navMeshSurface.BuildNavMesh();
|
||||
Debug.Log("NavMesh 生成完成");
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!showDebugGizmos) return;
|
||||
|
||||
// 绘制已放置模块的网格位置
|
||||
Gizmos.color = Color.blue;
|
||||
foreach (var kvp in placedPieces)
|
||||
{
|
||||
Vector3 worldPos = GridToWorld(kvp.Key);
|
||||
Gizmos.DrawWireCube(worldPos, Vector3.one * gridSize * 0.8f);
|
||||
}
|
||||
|
||||
// 绘制开放连接点
|
||||
Gizmos.color = Color.red;
|
||||
foreach (var connection in openConnections)
|
||||
{
|
||||
Vector3 worldPos = connection.localPosition;
|
||||
Gizmos.DrawSphere(worldPos, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GridToWorld(Vector3Int gridPos)
|
||||
{
|
||||
return new Vector3(gridPos.x * gridSize, gridPos.y * gridSize, gridPos.z * gridSize);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user