文档
2025GMTK
2025年GMTKGameJam的开发记录
目录
尝试制作了一些音效,感觉还是挺好玩的,可以积累一些自己的音效库以备不时之需
用射线检测制作了3D卡牌效果,感觉有潜力做成一个有意思的交互(无缝3D交互),逻辑:发射RAY检测物体层级并且是否带xx组件,如果是则执行对应点击反应
private void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
if (Physics.Raycast(ray, out RaycastHit hit, 100f))
{
// ====== 按钮悬浮 ======
var btn = hit.collider.GetComponent<BuyButton3D>();
if (btn != null)
{
if (btn != lastHoveredButton)
{
lastHoveredButton?.OnHoverExit();
btn.OnHovered();
lastHoveredButton = btn;
}
// 离开卡片
lastHoveredCard?.OnHoverExit();
lastHoveredCard = null;
return;
}
// ====== 卡片悬浮 ======
var card = hit.collider.GetComponent<WorldShopItem>();
if (card != null)
{
if (card != lastHoveredCard)
{
lastHoveredCard?.OnHoverExit();
card.OnHovered();
lastHoveredCard = card;
}
// 离开按钮
lastHoveredButton?.OnHoverExit();
lastHoveredButton = null;
return;
}
}
// ====== 没有悬浮任何物体 ======
lastHoveredButton?.OnHoverExit();
lastHoveredCard?.OnHoverExit();
lastHoveredButton = null;
lastHoveredCard = null;
if (shopOpen && Keyboard.current.anyKey.wasPressedThisFrame)
{
// 检查是否是鼠标左键点击(不要误触)
if (Mouse.current.leftButton.wasPressedThisFrame) return;
// 关闭商店
CloseShop();
}
}
private void OnClick(InputAction.CallbackContext ctx)
{
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
if (Physics.Raycast(ray, out RaycastHit hit, 100f))
{
// 点击了购买按钮
var buyBtn = hit.collider.GetComponent<BuyButton3D>();
if (buyBtn != null)
{
buyBtn.OnClicked();
return;
}
// 点击了卡牌本体
if (((1 << hit.collider.gameObject.layer) & cardLayer) != 0)
{
var card = hit.collider.GetComponent<WorldShopItem>();
card?.OnClicked();
return;
}
// 点击了打开商店的物体
if (!shopOpen &&
((1 << hit.collider.gameObject.layer) & shopTriggerLayer) != 0 &&
!hit.collider.gameObject.name.Contains("Mask"))
{
OpenShop();
return;
}
}
// 点击空地则关闭
if (shopOpen)
{
CloseShop();
}
}
Unity免费插件Tween制作动效还挺方便,好像没比DoTween差多少
TMP字体抖动效果直接用了Updata去控制mesh偏移,下次可以试试K2S安利的插件
public IEnumerator ApplyJitterToText(TMP_Text targetText)
{
targetText.ForceMeshUpdate();
TMP_TextInfo textInfo = targetText.textInfo;
while (true)
{
targetText.ForceMeshUpdate();
for (int i = 0; i < textInfo.characterCount; i++)
{
TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
if (!charInfo.isVisible)
continue;
Vector3[] verts = textInfo.meshInfo[charInfo.materialReferenceIndex].vertices;
int vertexIndex = charInfo.vertexIndex;
Vector3 offset = Random.insideUnitCircle * jitterIntensity;
verts[vertexIndex] += offset;
verts[vertexIndex + 1] += offset;
verts[vertexIndex + 2] += offset;
verts[vertexIndex + 3] += offset;
}
for (int i = 0; i < textInfo.meshInfo.Length; i++)
{
TMP_MeshInfo meshInfo = textInfo.meshInfo[i];
meshInfo.mesh.vertices = meshInfo.vertices;
targetText.UpdateGeometry(meshInfo.mesh, i);
}
yield return new WaitForSeconds(1f / jitterSpeed);
}
}

await的用法:等待执行完再继续执行后面的代码,在做演出效果或者回合类型的上很合适
在 Unity 里,await 一般是配合 异步方法(async)使用的,用来让代码在等待某个异步任务完成后再继续执行,而不会卡住主线程(UI 和游戏逻辑照常运行)。
不过 Unity 自带的 API(像 WaitForSeconds、yield return)默认是 协程(Coroutine) 体系,不是 async/await。
如果你想在 Unity 里用 await,主要有两种方式:
1. 基础用法(纯 C# 异步)#
using System.Threading.Tasks;
using UnityEngine;
public class AwaitExample : MonoBehaviour
{
async void Start()
{
Debug.Log("开始任务");
await Task.Delay(2000); // 等待 2 秒(不会卡主线程)
Debug.Log("任务完成");
}
}
特点
async标记方法允许使用awaitTask.Delay是纯 C# 的延时(毫秒),不会阻塞主线程- 缺点是:不能直接等待 Unity 的 MonoBehaviour 生命周期/协程
2. 在 Unity 中等待帧或秒(推荐用 UniTask / Cysharp.Threading.Tasks)#
Unity 原生不支持 await 等待 WaitForSeconds,但你可以用 UniTask(一个专门给 Unity 做的 async/await 库)
安装方式(Unity 2021+):
- 打开 Package Manager
- Add package from git URL
https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask
使用示例
using Cysharp.Threading.Tasks;
using UnityEngine;
public class UniTaskExample : MonoBehaviour
{
async void Start()
{
Debug.Log("等待 2 秒...");
await UniTask.Delay(TimeSpan.FromSeconds(2));
Debug.Log("等待下一帧...");
await UniTask.Yield();
Debug.Log("等待直到条件满足...");
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space));
Debug.Log("空格被按下!");
}
}
好处
- 能直接
awaitUnity 异步逻辑(延时、下一帧、条件成立等) - 性能比协程更好(GC 少)
- 代码是顺序结构,可读性比嵌套协程高
3. 协程转 await(不装 UniTask)#
如果你不想装库,可以自己写个扩展,把协程转换为 Task:
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
public static class CoroutineExtensions
{
public static Task AsTask(this IEnumerator coroutine, MonoBehaviour owner)
{
var tcs = new TaskCompletionSource<bool>();
owner.StartCoroutine(Wrap(coroutine, tcs));
return tcs.Task;
}
private static IEnumerator Wrap(IEnumerator coroutine, TaskCompletionSource<bool> tcs)
{
yield return coroutine;
tcs.SetResult(true);
}
}
使用:
async void Start()
{
await MyCoroutine().AsTask(this);
Debug.Log("协程结束");
}
IEnumerator MyCoroutine()
{
yield return new WaitForSeconds(2);
}
4. 注意事项#
- Unity 主线程必须返回到 Update 循环,所以
await后的代码依然会在主线程继续运行 - 不能在
Update里频繁开async void,否则会有很多挂起任务 - 推荐用
async UniTask或async Task而不是async void(除非是事件回调) await是非阻塞的,和Thread.Sleep、yield return不同

Blender雕刻里的布料模拟有一个膨胀很适合做枕头类效果
