Refine student suit theme and make it ready for the next step
This commit is contained in:
parent
73499fde06
commit
4d51c4b3c8
@ -95,7 +95,7 @@
|
||||
},
|
||||
{
|
||||
"ActionId": "Administration",
|
||||
"LocationId": "AdministrationBuilding",
|
||||
"LocationId": "AdminBuilding",
|
||||
"DurationSeconds": 5.0,
|
||||
"HungerDelta": -0.20,
|
||||
"EnergyDelta": -0.40,
|
||||
|
||||
@ -19,6 +19,31 @@ public partial class CampusController : Node2D {
|
||||
private readonly List<Vector2I> _astarWalkableCells = new();
|
||||
private readonly List<CampusBehaviorAgent> _behaviorAgents = new();
|
||||
private readonly CampusBehaviorWorld _behaviorWorld = new();
|
||||
|
||||
private readonly Dictionary<CampusLocationId, CampusBuilding> _buildings = new() {
|
||||
{
|
||||
CampusLocationId.Laboratory,
|
||||
new CampusBuilding("Laboratory", new Rect2I(48, 72, 192, 160), new Vector2I(150, 196))
|
||||
}, {
|
||||
CampusLocationId.Library,
|
||||
new CampusBuilding("Library", new Rect2I(312, 64, 256, 160), new Vector2I(440, 196))
|
||||
}, {
|
||||
CampusLocationId.Canteen,
|
||||
new CampusBuilding("Canteen", new Rect2I(640, 64, 128, 96), new Vector2I(745, 166))
|
||||
}, {
|
||||
CampusLocationId.Dormitory,
|
||||
new CampusBuilding("Dormitory", new Rect2I(800, 64, 96, 96), new Vector2I(848, 192))
|
||||
},
|
||||
{ CampusLocationId.ArtificialLake, new CampusBuilding("ArtificialLake", new Rect2I(), new Vector2I(943, 179)) },
|
||||
{ CampusLocationId.CoffeeShop, new CampusBuilding("CoffeeShop", new Rect2I(), new Vector2I(160, 395)) }, {
|
||||
CampusLocationId.AdminBuilding,
|
||||
new CampusBuilding("AdminBuilding", new Rect2I(234, 312, 128, 96), new Vector2I(296, 452))
|
||||
}, {
|
||||
CampusLocationId.FootballField,
|
||||
new CampusBuilding("FootballField", new Rect2I(568, 320, 160, 160), new Vector2I(560, 300))
|
||||
}
|
||||
};
|
||||
|
||||
private readonly List<Vector2> _coveragePoints = new();
|
||||
private readonly CampusLocationRegistry _locationRegistry = new();
|
||||
private List<string> _archetypeIds = new();
|
||||
@ -164,7 +189,7 @@ public partial class CampusController : Node2D {
|
||||
RegisterLocation(locationsRoot, "Location_Dorm", CampusLocationId.Dormitory);
|
||||
RegisterLocation(locationsRoot, "Location_Lake", CampusLocationId.ArtificialLake);
|
||||
RegisterLocation(locationsRoot, "Location_Coffee", CampusLocationId.CoffeeShop);
|
||||
RegisterLocation(locationsRoot, "Location_Admin", CampusLocationId.AdministrationBuilding);
|
||||
RegisterLocation(locationsRoot, "Location_Admin", CampusLocationId.AdminBuilding);
|
||||
RegisterLocation(locationsRoot, "Location_Field", CampusLocationId.FootballField);
|
||||
}
|
||||
|
||||
@ -813,6 +838,13 @@ public partial class CampusController : Node2D {
|
||||
}
|
||||
}
|
||||
|
||||
private class CampusBuilding(string locationId, Rect2I pRegion, Vector2I entrance) {
|
||||
public string LocationId { get; } = locationId;
|
||||
public HashSet<string> MemberIds { get; set; } = [];
|
||||
public Rect2I PortraitRegion { get; } = pRegion;
|
||||
public Vector2I EntrancePoint { get; } = entrance;
|
||||
}
|
||||
|
||||
private sealed partial class DebugGridOverlay : Node2D {
|
||||
public CampusController CampusOwner { get; set; }
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://b0cu4fa7vohmw"]
|
||||
[gd_scene load_steps=10 format=3 uid="uid://b0cu4fa7vohmw"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ew4ig6hnrsau" path="res://scenes/CampusController.cs" id="1_controller"]
|
||||
[ext_resource type="PackedScene" uid="uid://cf6b1t8pujosf" path="res://scenes/ui-elements/log_panel.tscn" id="1_hi2p7"]
|
||||
[ext_resource type="Texture2D" uid="uid://brmthiu6rxhqc" path="res://res_src/campus.png" id="1_p4tmp"]
|
||||
[ext_resource type="PackedScene" uid="uid://db2qcx61nc0q4" path="res://scenes/ui-elements/top-bar.tscn" id="2_p4tmp"]
|
||||
[ext_resource type="PackedScene" uid="uid://drmjsqoy8htc8" path="res://scenes/ui-elements/task_list.tscn" id="3_4gjr3"]
|
||||
[ext_resource type="PackedScene" uid="uid://bmx4ukf3rmoi7" path="res://scenes/student_portrait_16_native.tscn" id="6_74kl0"]
|
||||
|
||||
[sub_resource type="NavigationPolygon" id="NavigationPolygon_8u8vn"]
|
||||
vertices = PackedVector2Array(956, 540, 4, 540, 420, 516, 460, 516, 380, 516, 204, 356, 204, 260, 228, 284, 228, 468, 4, 500, 100, 500, 380, 468, 308, 468, 132, 212, 132, 180, 172, 180, 172, 212, 244, 212, 244, 60, 268, 60, 268, 236, 428, 236, 428, 172, 452, 172, 452, 236, 612, 236, 612, 60, 636, 60, 636, 244, 708, 244, 708, 196, 732, 196, 732, 140, 756, 140, 756, 196, 772, 196, 772, 60, 796, 60, 796, 228, 836, 228, 836, 180, 860, 180, 860, 284, 828, 284, 932, 308, 932, 180, 956, 180, 956, 332, 828, 332, 828, 308, 828, 516, 804, 516, 804, 284, 572, 284, 572, 300, 548, 300, 548, 284, 956, 516, 116, 364, 124, 364, 124, 396, 196, 396, 196, 356, 180, 436, 180, 412, 140, 412, 284, 468, 284, 436, 308, 436, 140, 436, 4, 284, 4, 60, 28, 60, 28, 212, 100, 284, 116, 260, 460, 284, 420, 284)
|
||||
@ -2033,6 +2034,7 @@ visible = false
|
||||
tile_set = SubResource("TileSet_74kl0")
|
||||
|
||||
[node name="Locations" type="Node2D" parent="."]
|
||||
visible = false
|
||||
|
||||
[node name="Location_Lab" type="Node2D" parent="Locations"]
|
||||
position = Vector2(150, 196)
|
||||
@ -2057,3 +2059,16 @@ position = Vector2(296, 452)
|
||||
|
||||
[node name="Location_Field" type="Node2D" parent="Locations"]
|
||||
position = Vector2(560, 300)
|
||||
|
||||
[node name="PortraitContainer" type="Node" parent="."]
|
||||
|
||||
[node name="HFlowContainer" type="HFlowContainer" parent="PortraitContainer"]
|
||||
visible = false
|
||||
offset_left = 568.0
|
||||
offset_top = 320.0
|
||||
offset_right = 728.0
|
||||
offset_bottom = 480.0
|
||||
|
||||
[node name="StudentPortrait2" parent="PortraitContainer/HFlowContainer" instance=ExtResource("6_74kl0")]
|
||||
|
||||
[node name="StudentPortrait" parent="PortraitContainer/HFlowContainer" instance=ExtResource("6_74kl0")]
|
||||
|
||||
@ -908,7 +908,7 @@ public sealed class CampusBehaviorAgent {
|
||||
CampusLocationId.Dormitory => "宿舍",
|
||||
CampusLocationId.ArtificialLake => "人工湖",
|
||||
CampusLocationId.CoffeeShop => "咖啡店",
|
||||
CampusLocationId.AdministrationBuilding => "行政楼",
|
||||
CampusLocationId.AdminBuilding => "行政楼",
|
||||
CampusLocationId.FootballField => "足球场",
|
||||
CampusLocationId.RandomWander => "校园",
|
||||
_ => "校园"
|
||||
|
||||
@ -18,7 +18,7 @@ public enum CampusLocationId {
|
||||
Dormitory, // 宿舍
|
||||
ArtificialLake, // 人工湖
|
||||
CoffeeShop, // 咖啡店
|
||||
AdministrationBuilding, // 行政楼
|
||||
AdminBuilding, // 行政楼
|
||||
FootballField, // 足球场
|
||||
RandomWander // 随机漫游
|
||||
}
|
||||
@ -110,7 +110,7 @@ public sealed class CampusBehaviorConfig {
|
||||
public CampusActionConfig GetActionConfig(CampusActionId id) {
|
||||
if (_actionLookup.Count == 0) BuildLookup();
|
||||
|
||||
return _actionLookup.TryGetValue(id, out var config) ? config : null;
|
||||
return _actionLookup.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
private void BuildLookup() {
|
||||
|
||||
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Godot;
|
||||
using Views;
|
||||
|
||||
/// <summary>
|
||||
/// 校园学生角色控制器
|
||||
@ -12,6 +13,19 @@ public partial class CampusStudent : CharacterBody2D {
|
||||
/// </summary>
|
||||
private readonly List<Vector2> _gridPath = new();
|
||||
|
||||
/// <summary>
|
||||
/// 外观主题
|
||||
/// </summary>
|
||||
private readonly SuitTheme _theme = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取外观主题ID
|
||||
/// </summary>
|
||||
/// <returns>主题ID</returns>
|
||||
public ulong GetSuitThemeId() {
|
||||
return _theme.GetThemeId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画播放器
|
||||
/// </summary>
|
||||
@ -112,10 +126,6 @@ public partial class CampusStudent : CharacterBody2D {
|
||||
/// </summary>
|
||||
private float _stuckTimer;
|
||||
|
||||
private Sprite2D[] _theme;
|
||||
|
||||
private ulong _themeId;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用物理移动
|
||||
/// </summary>
|
||||
@ -181,6 +191,8 @@ public partial class CampusStudent : CharacterBody2D {
|
||||
public override void _Ready() {
|
||||
_animationPlayer = GetNodeOrNull<AnimationPlayer>("AnimationPlayer");
|
||||
_campusController = GetTree()?.CurrentScene as CampusController;
|
||||
_theme.IsPortrait = false;
|
||||
_theme.Use16 = Use16X16Sprites;
|
||||
CacheSprites();
|
||||
ConfigureCollision();
|
||||
|
||||
@ -377,16 +389,12 @@ public partial class CampusStudent : CharacterBody2D {
|
||||
/// </summary>
|
||||
public void ApplyRandomTheme() {
|
||||
// 随机替换身体与配件贴图,形成不同主题外观
|
||||
if (_theme == null) CacheSprites();
|
||||
if (_theme.IsEmpty()) CacheSprites();
|
||||
|
||||
Debug.Assert(_theme != null, nameof(_theme) + " != null");
|
||||
|
||||
_themeId = 0;
|
||||
for (var typeId = 0; typeId < (int)Res.Type.ResTypeMax; typeId++) {
|
||||
var ret = Res.GetResourcePathWithId(0, (Res.Type)typeId, Use16X16Sprites, false, true);
|
||||
_theme[typeId].Texture = ResourceLoader.Load<Texture2D>(ret.Path);
|
||||
_themeId |= (ret.Id & 0xff) << (8 * typeId);
|
||||
}
|
||||
for (Res.Type typeId = 0; typeId < Res.Type.ResTypeMax; typeId++)
|
||||
_theme.ApplyComponent(typeId, Res.GetResourcePathWithId(0, typeId, Use16X16Sprites, false, true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -394,15 +402,12 @@ public partial class CampusStudent : CharacterBody2D {
|
||||
/// </summary>
|
||||
private void CacheSprites() {
|
||||
// 缓存子节点引用,避免每帧查找
|
||||
// 顺序需要和Res.Type的枚举顺序一致
|
||||
_theme = [
|
||||
GetNode<Sprite2D>("parts/accessory"),
|
||||
GetNode<Sprite2D>("parts/body"),
|
||||
GetNode<Sprite2D>("parts/eye"),
|
||||
GetNode<Sprite2D>("parts/hairstyle"),
|
||||
GetNode<Sprite2D>("parts/outfit"),
|
||||
GetNode<Sprite2D>("parts/smartphone")
|
||||
];
|
||||
_theme.CacheComponent(Res.Type.Accessory, GetNode<Sprite2D>("parts/accessory"));
|
||||
_theme.CacheComponent(Res.Type.Body, GetNode<Sprite2D>("parts/body"));
|
||||
_theme.CacheComponent(Res.Type.Eye, GetNode<Sprite2D>("parts/eye"));
|
||||
_theme.CacheComponent(Res.Type.Hair, GetNode<Sprite2D>("parts/hairstyle"));
|
||||
_theme.CacheComponent(Res.Type.Outfit, GetNode<Sprite2D>("parts/outfit"));
|
||||
_theme.CacheComponent(Res.Type.Phone, GetNode<Sprite2D>("parts/smartphone"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -273,6 +273,12 @@ public partial class Lab : Node2D {
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的地图块位置
|
||||
/// </summary>
|
||||
/// <param name="nType">地图节点类型</param>
|
||||
/// <param name="idx">索引</param>
|
||||
/// <returns>地图块位置</returns>
|
||||
public Vector2I GetTypedBlock(MapNodeType nType, uint idx) {
|
||||
switch (nType) {
|
||||
default:
|
||||
|
||||
@ -1,30 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Views;
|
||||
|
||||
public partial class StudentPortrait16Native : Node2D {
|
||||
/// <summary>
|
||||
/// 饰品精灵
|
||||
/// </summary>
|
||||
private Sprite2D _accessory;
|
||||
|
||||
/// <summary>
|
||||
/// 动画播放器
|
||||
/// </summary>
|
||||
private AnimationPlayer _animationPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// 身体精灵
|
||||
/// </summary>
|
||||
private Sprite2D _body;
|
||||
|
||||
/// <summary>
|
||||
/// 眼睛精灵
|
||||
/// </summary>
|
||||
private Sprite2D _eye;
|
||||
|
||||
/// <summary>
|
||||
/// 发型精灵
|
||||
/// </summary>
|
||||
private Sprite2D _hairstyle;
|
||||
private readonly SuitTheme _theme = new();
|
||||
|
||||
/// <summary>
|
||||
/// 主题ID
|
||||
/// </summary>
|
||||
public ulong ThemeId {
|
||||
get => _theme.GetThemeId();
|
||||
set => _theme.ApplyTheme(value);
|
||||
}
|
||||
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
public override void _Ready() {
|
||||
@ -41,10 +33,10 @@ public partial class StudentPortrait16Native : Node2D {
|
||||
/// </summary>
|
||||
private void CacheSprites() {
|
||||
// 缓存子节点引用,避免每帧查找
|
||||
_body = GetNode<Sprite2D>("parts/body");
|
||||
_hairstyle = GetNode<Sprite2D>("parts/hairstyle");
|
||||
_eye = GetNode<Sprite2D>("parts/eye");
|
||||
_accessory = GetNode<Sprite2D>("parts/accessory");
|
||||
_theme.CacheComponent(Res.Type.Accessory, GetNode<Sprite2D>("parts/accessory"));
|
||||
_theme.CacheComponent(Res.Type.Body, GetNode<Sprite2D>("parts/body"));
|
||||
_theme.CacheComponent(Res.Type.Eye, GetNode<Sprite2D>("parts/eye"));
|
||||
_theme.CacheComponent(Res.Type.Hair, GetNode<Sprite2D>("parts/hairstyle"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -62,7 +54,7 @@ public partial class StudentPortrait16Native : Node2D {
|
||||
/// </summary>
|
||||
/// <param name="animationName">动画名称</param>
|
||||
private void OnAnimationFinished(StringName animationName) {
|
||||
_animationPlayer?.Play(animationName);
|
||||
_animationPlayer?.Play(animationName);
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
|
||||
89
scripts/Views/SuitTheme.cs
Normal file
89
scripts/Views/SuitTheme.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace Views;
|
||||
|
||||
/// <summary>
|
||||
/// 套装主题管理类
|
||||
/// </summary>
|
||||
public class SuitTheme {
|
||||
private readonly Dictionary<Res.Type, Sprite2D> _sprites = new();
|
||||
private readonly Dictionary<Res.Type, ushort> _theme = new();
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用16x16精灵
|
||||
/// </summary>
|
||||
public bool Use16 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为头像
|
||||
/// </summary>
|
||||
public bool IsPortrait { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 缓存组件精灵
|
||||
/// </summary>
|
||||
/// <param name="type">资源类型</param>
|
||||
/// <param name="sprite">精灵对象</param>
|
||||
public void CacheComponent(Res.Type type, Sprite2D sprite) {
|
||||
_sprites[type] = sprite;
|
||||
if (_theme.TryGetValue(type, out var value)) {
|
||||
sprite.Texture = ResourceLoader.Load<Texture2D>(Res.GetResourcePathWithId(value, type).Path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析主题ID
|
||||
/// </summary>
|
||||
/// <param name="themeId">主题ID</param>
|
||||
private void _extractThemeId(ulong themeId) {
|
||||
for (Res.Type typeId = 0; typeId < Res.Type.ResTypeMax; typeId++)
|
||||
_theme[typeId] = (ushort)((themeId >> (8 * (int)typeId)) & 0xfful);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取主题ID
|
||||
/// </summary>
|
||||
/// <returns>主题ID</returns>
|
||||
public ulong GetThemeId() {
|
||||
ulong result = 0;
|
||||
for (Res.Type typeId = 0; typeId < Res.Type.ResTypeMax; typeId++) {
|
||||
if (!_theme.TryGetValue(typeId, out var value)) continue;
|
||||
result |= (ulong)(value & 0xff) << (8 * (int)typeId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用主题
|
||||
/// </summary>
|
||||
/// <param name="themeId">主题ID</param>
|
||||
public void ApplyTheme(ulong themeId) {
|
||||
_extractThemeId(themeId);
|
||||
foreach (var kvp in _sprites)
|
||||
if (_theme.TryGetValue(kvp.Key, out var value))
|
||||
kvp.Value.Texture = ResourceLoader.Load<Texture2D>(
|
||||
Res.GetResourcePathWithId(value, kvp.Key, Use16, IsPortrait).Path
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用组件资源
|
||||
/// </summary>
|
||||
/// <param name="type">资源类型</param>
|
||||
/// <param name="resPath">资源路径对象</param>
|
||||
public void ApplyComponent(Res.Type type, Res.ResPathWithId resPath) {
|
||||
if (!_sprites.TryGetValue(type, out var value)) return;
|
||||
value.Texture = ResourceLoader.Load<Texture2D>(resPath.Path);
|
||||
_theme[type] = (ushort)resPath.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为空
|
||||
/// </summary>
|
||||
/// <returns>如果是空则返回true</returns>
|
||||
public bool IsEmpty() {
|
||||
return _sprites.Count == 0;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user