跳到主要内容

📦Task 任务

翻译和补充: 泡泡
原作者: Pokeemerald Wiki by tustin2121


Task 任务系统

正如在游戏循环中讨论的那样,主回调通常不包含任何游戏逻辑。这是因为3代引擎使用Task系统在给定屏幕期间执行逻辑操作。

让我们以缆车过场动画为例,了解一下这些任务的工作原理。

在过场动画初始化期间,您将看到以下几行:

SetMainCallback2(CB2_CableCar);
CreateTask(Task_CableCar, 0);
if (!GOING_DOWN)
sCableCar->bgTaskId = CreateTask(Task_AnimateBgGoingUp, 1);
else
sCableCar->bgTaskId = CreateTask(Task_AnimateBgGoingDown, 1);

mainCallback2设置后,游戏会创建两个Task

  • 第一个Task函数为Task_CableCar,并以优先级0运行(将首先运行)
  • 第二个Task是使支架上或下运动的动画函数,优先级为1,这意味着它每一帧在Task_CableCar之后运行函数

❗请注意,该CreateTask函数返回一个Task ID,它只是一个简单的u8字节。该ID可用于索引内存中的gTasks以获取有关我们当前Task的信息。

在创建的第二个Task中,我们保存此ID以供之后使用,从技术上讲,此ID是Task在gTasks中创建的位置,但由于Task列表是一个链接列表,因此它在阵列中的位置与Task的运行顺序无关(Task的执行顺序已经由传递给CreateTask的优先级决定)此ID不能被修改,而只适用于存储和访问信息。

使用任务ID,我们可以访问Task中的数据:

struct Task
{
TaskFunc func; // 该Task的程序
bool8 isActive; // 该Task是否运行
u8 prev;
u8 next;
u8 priority;
s16 data[NUM_TASK_DATA]; // 该Task的data数组
};

通常情况下,我们只需要关注funcdata这两项,其他的都由Task系统分配

  • 指针func指向我们传递给CreateTask的函数回调。该函数必须为void Task_FunctionName(u8 taskId)的形式,在pokeemerald反编译项目中,每个用作Task回调的函数都命名为Task_XXX,因此您应该很容易看出哪些是Task回调。调用此函数时,它会传递taskId,所以我们不必在该task中存储这个Task的taskId。但是,我们可以存储其他的taskId,稍后将讨论。

  • data数组是一个16个s16类型数据的数组,我们可以在其中放入任何想要的数据。这些数据供我们自己使用。在pokeemerald代码库中,您经常会看到这些数据字段的引用方式如下:

#define tTimer   data[0]
#define tState data[1]

static void Task_ExampleTask(u8 taskId)
{
// 直接访问
gTasks[taskId]->tTimer++; // 实际上是gTasks[taskId]->data[0]++;

// 或者可以将data设置为指针
s16 *data = gTasks[taskId].data;
tState = 0; // 实际上是: data[1] = 0;
}

#undef tState
#undef tTimer

您会发现在代码库中这两种策略可以互换使用。

函数func每帧调用一次,输入的数据data可用于保持帧之间的状态。您可以将其中一个数据字段设为状态,并将函数设为基于状态的switch,或者您可以将其他函数分配给func,分配的函数将在下一帧运行:

static void Task_WaitForFade(u8 taskId)
{
if (!gPaletteFade.active)
{
// 在淡出淡入效果结束的下一帧将当前Task的函数设置为Task_HandlePlayerInput
gTasks[taskId].func = Task_HandlePlayerInput; // 更改Task的函数,之后runTask将会运行Task_HandlePlayerInput函数而不是Task_WaitForFade
}
}

static void Task_HandlePlayerInput(u8 taskId)
{
//此任务将使用与上面相同的任务ID和数据运行
//任何数据都来自上面的函数
}
提示

Task和一开始createTask里的函数并不绑定,并不是func变了taskIddata的数据就会变

如果您想要一个经常使用的示例,请查看src/credits.c

您还将在该文件中看到使用data字段来存储它创建的其他Task的TaskID。

gTasks[taskId].tTaskId_ShowMons = CreateTask(Task_ShowMons, 0); // 创建一个新Task,将Id存储到当前task中的data里
gTasks[gTasks[taskId].tTaskId_ShowMons].tState = 1; // 设置前面那个创建的task中的data
gTasks[gTasks[taskId].tTaskId_ShowMons].tMainTaskId = taskId; // 在前面那个创建的task的data里储存当前task的Id

最后,一旦任务完成,可以使用DestroyTask删除当前这个task:

DestroyTask(taskId);
DestroyTask(sCableCar->bgTaskId);
SetMainCallback2(CB2_EndCableCar);

这会将Task设置为非活动状态,并且它将不再在后续帧上运行。

请确保在清理屏幕或交互时执行此操作;而ResetTasks会销毁所有Task,您会发现它通常在清理场景时运行,只是为了确保不会意外地将Task留在活动状态。

提示

原版Task最多16个,请确保不会超标



相关函数

代码文件:src/task.c


ResetTasks

void ResetTasks(void);

💬初始化所有gTask的数据,并给所有的data赋值0,该函数用于初始化场景时清理上一个场景的Task,无须再使用DestroyTask一个个去清理



CreateTask

u8 CreateTask(TaskFunc func, u8 priority);

💬创建一个Task,并返回创建的TaskId,当创建失败时会返回0

参数类型介绍
funcTaskFunctask的回调函数地址
priorityu8task的执行优先级

✔️返回值:

  • u8: 创建的TaskId,当创建失败时会返回0


DestroyTask

void DestroyTask(u8 taskId);

💬清除指定Id的Task,仅在该Task处于运行状态时

参数类型介绍
taskIdu8需要清除的taskId


RunTasks

void RunTasks(void);

💬根据优先级顺序运行所有task的func函数,通常放在mainCallback2中实时运行



SetTaskFuncWithFollowupFunc

void SetTaskFuncWithFollowupFunc(u8 taskId, TaskFunc func, TaskFunc followupFunc);

💬修改指定id的Task的func,并将给定的子回调函数存储到data数组的最后两个里,由于data数组是2字节,而函数指针是4字节,所以此指针会将高低位拆开存放

之后可以配合SwitchTaskToFollowupFun函数将当前Task的func赋值为存储的子回调函数

参数类型介绍
taskIdu8需要设置的taskId
funcTaskFunc设置的回调函数地址
followupFuncTaskFunc设置的子回调函数地址


SwitchTaskToFollowupFunc

void SwitchTaskToFollowupFunc(u8 taskId);

💬将指定id的Task的func修改为存储的子回调函数,配合SetTaskFuncWithFollowupFunc使用

参数类型介绍
taskIdu8需要切换的taskId


FuncIsActiveTask

bool8 FuncIsActiveTask(TaskFunc func);

💬根据传入的函数判断是否有运行中的Task的func与该函数相同

参数类型介绍
funcTaskFunc要检查的task的回调函数

✔️返回值:

  • bool8: 存在一个或者多个运行中的task使用了该函数作为func时返回TRUE,否则返回FALSE


FindTaskIdByFunc

u8 FindTaskIdByFunc(TaskFunc func);

💬根据传入的函数判断是否有运行中的Task的func与该函数相同,如果有匹配的Task,返回其中第一个task的Id

参数类型介绍
funcTaskFunc要检查的task的回调函数

✔️返回值:

  • bool8: 存在一个或者多个运行中的task使用了该函数作为func时返回该Task的Id,否则返回TASK_NONE


SetWordTaskArg

void SetWordTaskArg(u8 taskId, u8 dataElem, u32 value);

💬在指定id的task的data数组里写入一个4字节的数据,dataElem为空闲的data数组下标,会占用该下标下标+1的位置

参数类型介绍
taskIdu8需要写入数据的taskId
dataElemu8data数组下标起始
valueu324字节长度的数据

请确保该下标不会超过NUM_TASK_DATA - 1



GetWordTaskArg

u32 GetWordTaskArg(u8 taskId, u8 dataElem);

💬获取指定id的task的data数组dataElemdataElem+1下标里存储的值,组合成一个4字节数据返回

参数类型介绍
taskIdu8需要获取数据的taskId
dataElemu8data数组下标起始

✔️返回值:

  • u32: 组合的4字节数据

请确保该下标不会超过NUM_TASK_DATA - 1