nancygold's Journal
 
[Most Recent Entries] [Calendar View] [Friends View]

Saturday, May 11th, 2024

    Time Event
    3:01a
    Spell of Mastery Rewrite Progress
    Previously I have split the UI and game objects across multiple tables.
    Now I'm refactoring the game logic into systems.
    That immediately resulted in about 1/2 of the code being thrown away.
    In addition, all these
    is_null_pointer(object) ? default_value : field[object]
    are now handled by the table code.
    And most common null pointer errors are now impossible, since the systems just won't get called on absent fields.

    It was really surprising that something as simple playing a 2d animation could be so hard to do right on the first try, when considered in a general context. And it easy to mess up, like making planner depending on the currently playing animation. For example, I had actuator waiting for the animation to reach specific stages. So now I also had to redo all my animation scripts to not including any game logic. And all the online guides on animation systems recommend the hierarchical state machines cargo cult, which is a really bad idea architecturally wise, since it breeds non-uniformity further, requiring maintaining a HSM for each class of entities.

    The game logic, which was previously a convoluted spaghetti mess, is now split into a conveyor tree, where each stage depends only on the previous stage:
    - Player AI gives orders to entities.
    - Planner pursues the order, and issues appropriate actions;
    - Actuator, like attack and move from cell to cell.
    Optionally it can awaken the planner on action completion;
    Everything after actuator is optional and can be turned off for the server side code.
    - Animator analyses the actuator's state and determines which animation to play.
    Optionally it can awaken the actuator;
    - Performer plays specific animation, by providing the frames to renderer.
    Optionally it can awaken the animator when the animation finishes;
    - Renderer, plays the frame, picked by performer.

    For me the additional challenge is the discrete nature of the game, where all entities act inside the quantified time units. And ECS helped to clean up that logic. I.e. while the entity has already consumed the allocated quants, some of the animations, like idle or move, are still expected to play. basically entities walk in place (Final Fantasy Tactics style) to express that they perform movement order. Same way, poisoned units should have poison animation playing.

    TODO1: integrating the physics with the actuator actions. I.e. a can a falling unit act? Like activating a parachute?
    TODO2: introduce the phased methods, instead of the clunky update_systems.

    Basically the below code could be organized as a set of methods like, `entity.motilize phase.each_cycle = ...`
    update_systems Me =
      update_custom_systems Me
    
      //satellites
      for U each(add_cover){unit_}:  add_cover U
      for U each(add_shadow){unit_}: add_shadow U
      for U each(remove_sat){unit_}: remove_sat U
      for U each(place_sat){unit_}:  place_sat U
    
      //properties
      for U each(motilize){unit_}:      motilize U
      for U each(needs_globals){unit_}: U.add_global_acts
    
    
      //activation
      for U each(active){unit_}:    U.do_activation
      for U each(goal){unit_}:      U.break_idle
      for U each(ordered){unit_}:   U.break_idle
    
      //physics
      for U each(falling){unit_}:   update_falling U
    
      //actions
      for U each(members_){unit_}:  update_squad U
      for U each(planning){unit_}:  update_planning U
      for U each(ordered){unit_}:   take_order U
      for U each(acting){unit_}:    update_acting U
      for U each(actpost){unit_}:   update_actpost U
      for U each(pursued){unit_}:   update_pursued U
      for U each(enchng){unit_}:    update_enchng U
    
      for U each(sqid){unit_}:      update_sqid U
      for U each(host){unit_}:      update_hosted U
    
      //graphics
      for U each(animator){unit_}:   U.upd_animator
      for U each(animated){unit_}:   U.upd_animated
      for U each(anm_float){unit_}:  U.reset_visl
      for U each(made_step){unit_}:  made_step U //set by upd_animated
      //for U each(randidle){unit_}:  U.rand_anim_step
    
      for U each(fade_inc){unit_}:   U.update_fade
      for U each(suffers){unit_}:    upd_suffers U
    


    Current Mood: contemplative
    Current Music: Kuldahar Theme
    11:35a
    Where can one donate to IDF?
    Where can one donate to IDF?

    Asking for a friend.

    Current Mood: contemplative
    2:16p
    Alternatives to ECS
    Some people evade ECS by using hierarchical state machines (HSMs), which makes processing complex objects somewhat manageable:
    https://statecharts.dev/state-machine-state-explosion.html
    https://statecharts.dev/what-is-a-statechart.html
    A compound state can be split up into completely separate (“orthogonal”) regions. Each region specifies its own state machine. When a state machine enters such a state, it also enters all of the regions of the state at the same time.

    They still convolve barely related concepts (i.e. "change" and "validity" in that example), so HSMs fall apart for larger projects (larger than a Pong game).

    I think something as simple and basic as a Tetris game or a chat server core will be a challenge to represent with HSMs.

    Like sawing a tree with a pocked knife.

    Of course all these paradigms can be combined together. Nothing stops you from using ECS with HSM.

    In fact, I implemented the entirety of classic OOP using ECS.
    Since I've transitioned existing code;
    And I want object aspects to be tangible and have associated methods.

    Current Mood: amused

    << Previous Day 2024/05/11
    [Calendar]
    Next Day >>

About LJ.Rossia.org