This commit is contained in:
2026-07-19 22:12:53 -05:00
parent e80d693d85
commit 373f1c1010
24 changed files with 1646 additions and 16 deletions
@@ -9,12 +9,18 @@
#include "entity/entitymanager.h"
#include "entity/component/physics/entityphysics.h"
#include "entity/component/display/entitycamera.h"
#include "entity/component/display/entityposition.h"
#include "input/input.h"
#include "util/memory.h"
#define ENTITY_PLAYER_MOVE_SPEED_DEFAULT 4.0f
#define ENTITY_PLAYER_JUMP_IMPULSE_DEFAULT 6.0f
// Below this squared magnitude, movement input is treated as "not moving"
// and the player keeps facing whichever way it last faced, rather than
// snapping to some default orientation.
#define ENTITY_PLAYER_MOVE_ROTATE_THRESHOLD_SQ 0.0001f
void entityPlayerInit(
entitymanager_t *mgr,
const entityid_t entityId,
@@ -85,4 +91,58 @@ void entityPlayerUpdate(
velocity[0] = moveDir[0] * player->moveSpeed;
velocity[2] = moveDir[1] * player->moveSpeed;
entityPhysicsSetVelocity(mgr, entityId, physComp, velocity);
// Face the direction of movement. Only updated while actually moving --
// releasing input leaves the player facing whichever way it last moved.
float_t moveDirMagSq = moveDir[0] * moveDir[0] + moveDir[1] * moveDir[1];
if(moveDirMagSq < ENTITY_PLAYER_MOVE_ROTATE_THRESHOLD_SQ) return;
componentid_t posComp = entityGetComponent(
mgr, entityId, COMPONENT_TYPE_POSITION
);
if(posComp == COMPONENT_ID_INVALID) return;
vec3 rotation;
entityPositionGetLocalRotation(mgr, entityId, posComp, rotation);
// The player's "front" is +local Z, not -Z (entityCameraGetForward's
// convention is for cameras, which look down -Z -- the player model
// faces the opposite way), which for a pure yaw rotation works out to
// (sin(yaw), 0, cos(yaw)) -- invert that to solve for the yaw facing
// moveDir.
rotation[1] = atan2f(moveDir[0], moveDir[1]);
entityPositionSetLocalRotation(mgr, entityId, posComp, rotation);
}
errorret_t entityPlayerSerialize(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
yyjson_mut_doc *doc,
yyjson_mut_val *json
) {
entityplayer_t *player = entityPlayerGet(mgr, entityId, componentId);
yyjson_mut_obj_add_real(doc, json, "moveSpeed", player->moveSpeed);
yyjson_mut_obj_add_real(doc, json, "jumpImpulse", player->jumpImpulse);
errorOk();
}
errorret_t entityPlayerDeserialize(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
yyjson_val *json
) {
entityplayer_t *player = entityPlayerGet(mgr, entityId, componentId);
yyjson_val *v;
if((v = yyjson_obj_get(json, "moveSpeed"))) {
player->moveSpeed = (float_t)yyjson_get_num(v);
}
if((v = yyjson_obj_get(json, "jumpImpulse"))) {
player->jumpImpulse = (float_t)yyjson_get_num(v);
}
errorOk();
}
@@ -7,6 +7,8 @@
#pragma once
#include "entity/entitybase.h"
#include "error/error.h"
#include "yyjson.h"
typedef struct {
float_t moveSpeed;
@@ -48,8 +50,14 @@ entityplayer_t *entityPlayerGet(
* and drives the entity's physics velocity, relative to the current
* camera's horizontal facing (so "up" always moves away from the camera
* and "left"/"right" strafe relative to its view), rather than fixed world
* axes. No-op if the entity has no physics component. Registered
* automatically as an update callback by entityPlayerInit.
* axes. No-op if the entity has no physics component.
*
* Also yaws the entity's position component (if any) to face the
* direction of movement. Facing is only updated while there's actual
* movement input -- releasing input leaves the entity facing whichever
* way it last moved, rather than snapping to a default orientation.
*
* Registered automatically as an update callback by entityPlayerInit.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The entity ID.
@@ -62,3 +70,40 @@ void entityPlayerUpdate(
const componentid_t componentId,
void *user
);
/**
* Serializes the player's "moveSpeed" and "jumpImpulse" into the given
* JSON object.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The entity ID.
* @param componentId The component ID.
* @param doc The mutable JSON document to allocate values from.
* @param json The JSON object to write the component's fields into.
* @return Error state.
*/
errorret_t entityPlayerSerialize(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
yyjson_mut_doc *doc,
yyjson_mut_val *json
);
/**
* Reads "moveSpeed" and "jumpImpulse" (see entityPlayerSerialize()) from
* the given JSON object and applies whichever are present, leaving the
* rest at their current values.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The entity ID.
* @param componentId The component ID.
* @param json The JSON object to read the component's fields from.
* @return Error state.
*/
errorret_t entityPlayerDeserialize(
entitymanager_t *mgr,
const entityid_t entityId,
const componentid_t componentId,
yyjson_val *json
);
+4 -1
View File
@@ -15,5 +15,8 @@
// Init function (optional)
// Dispose function (optional)
// Render function (optional)
// Serialize function (optional)
// Deserialize function (optional)
X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL)
X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL,
entityPlayerSerialize, entityPlayerDeserialize)
+25 -1
View File
@@ -8,9 +8,33 @@
#include "game/game.h"
#include "scene/scene.h"
#include "scene/overworldscene.h"
#include "assert/assert.h"
#include "asset/asset.h"
#include "yyjson.h"
#define GAME_TEST_SCENE_ASSET "scenes/test.json"
errorret_t gameInit(void) {
sceneSetActive(overworldSceneCreate());
// overworldSceneCreate();
sceneid_t testSceneId = sceneCreate();
// Exercises sceneDeserialize() end-to-end: a camera looking at a
// colored cube that falls under gravity onto a static ground plane,
// described entirely as JSON rather than built with the entity API.
assetentry_t *sceneEntry = assetLock(
GAME_TEST_SCENE_ASSET, ASSET_LOADER_TYPE_JSON, NULL
);
errorret_t ret = assetRequireLoaded(sceneEntry);
if(errorIsOk(ret)) {
ret = sceneDeserialize(
testSceneId, yyjson_doc_get_root(sceneEntry->data.json)
);
}
assetUnlockEntry(sceneEntry);
errorChain(ret);
sceneSetActive(testSceneId);
errorOk();
}