Skip to content

Instantly share code, notes, and snippets.

@MinusKelvin
Last active April 14, 2018 22:07
Show Gist options
  • Save MinusKelvin/4388386d32953da4a6431f9665088ffd to your computer and use it in GitHub Desktop.
Save MinusKelvin/4388386d32953da4a6431f9665088ffd to your computer and use it in GitHub Desktop.
Analysis for gamelang

GameLang (working title)

A programming language designed for game development.

QuickNav

  1. Introduction
  2. State Machines
  3. Input Handling
  4. Entity Component Model
  5. Systems
  6. Events
  7. Possible Future Features

Description

GameLang is a high-level domain specific language designed specifically for creating games. The language does this by providing language-level support for a flexible and loosely-coupled architecture based on events, state machines, and entity-component systems. The language additionally provides a flexible system for handling user input and an interpolation/extrapolation fixed-timestep gameloop. The language may later provide ways of doing graphics, resource management, audio, dialog trees, UI, and other features, but these are not being considered at this time.

The language is currently expected to be transpiled to high level general purpose languages. Since GameLang is not general purpose and does not provide mechanisms to perform a variety of tasks that are common in games (such as high-performance graphics), transpiling it to a general purpose language allows the code generated with GameLang to be interfaced with in a relatively simple manner.

Philosophy

Design patterns are essentially a template for solving a problem that occurs in many different programs. The core philosophy of GameLang is that design patterns are consequences of shortcomings in the programming language's ability to solve the problems of a particular domain. For example, consider the Command design pattern from Design Patterns; it is essentially a second-class closure. Another example would be manually pushing parameters to the stack before calling a function in assembly, whereas in C the compiler handles those details for you. Language features can also replace domain-specific architectural design patterns, such as the Model-View-Controller pattern which has been solved in browsers by a combination of HTML, CSS, and JavaScript.

State Machines

TODO / Notes

  • Kind of a sum type
  • Expected use cases:
    • Menus (options vs pause) (singleton)
    • Game Context (play vs inventory) (instance (?))
    • AI/Player (on ground vs falling) (component)
  • Must describe data available to the state
  • Would like these to describe transition functions. Possible ways of describing these:
    • event -> transition (current idea)
    • event -> code that may do transition
    • Allowing for both would be good, since sometimes the transition needs to be calculated or not even done based on e.g. event variables, state variables. I would like to keep imperative code out of the part of GameLang, however
  • Might be useful to be able to split a single machine across multiple files; the code for a single state may be rather large (e.g. GUI code)
  • Default/starting state for the machine to be in
  • Some states would benefit from being able to go back to a previous state
  • Events for switching to/away
  • Timers for transitions: switch away happens, timer counts down, switch to happens. Need to consider what systems should get run and what context applies (if any) during transitions.

Concept psuedocode:

machine Menu {
    default state Main {
        event options -> Options(null)

        event play -> Gameplay(new GameContext())
    }

    stack state machine Options(context: GameContext?) {
        default state Toplevel {
            event back (Menu) -> back

            event controls -> Controls
        }

        state Controls {
            event back -> Toplevel
        }
    }

    state Gameplay(context: GameContext) {
        event pause -> Pause(context)
    }

    state Pause(context: GameContext) {
        event options -> Options(context)

        event back -> Gameplay(context)
    }
}

Input Handling

TODO / Notes

  • Should essentially unify keyboard, mouse, and controller inputs together
  • It is desirable for an action to have more than one input bound to it (e.g. confirm could be spacebar or controller A button or enter)
  • It is desirable for an input to be bound to more than one action (e.g. jump and confirm and advance dialog)
  • It is undesirable for two actions usable in the same context to have the same input bound to it (e.g. jump and open inventory)
  • The above three points suggest having a set of actions bound to contexts, and then requiring that no actions in a context have the same input bound to it.

Current idea for abstraction between inputs and actions uses the following three fundamentals, with examples:

  • Event controls (event)
    • Scroll up/down
    • Button pressed/released
    • Axis crossed threshold
    • For e.g. pause (in any game)
  • Button controls (boolean)
    • Keyboard keys, mouse buttons
    • Axis above threshold
    • For e.g. charge attack (in an action game)
  • Axis controls (float, 0.0 to 1.0)
    • Stick positive/negative horizontal/vertical direction
    • Analog triggers
    • Button (1 when held, 0 when not held)
    • For e.g. accelerate car (in a driving game)

Common control types derivable for the three fundamentals:

  • Full axis controls (float, -1.0 to 1.0), composed from a negative axis and a positive axis
    • LS horizontal negative and LS horizontal positive
    • Keyboard left arrow and keyboard right arrow
    • For e.g. move (in a side-scroller)
  • Directional controls (9-state enum), composed from 4 axes, one for each cardinal direction
    • Dpad
    • LS used as Dpad
    • WASD
    • For e.g. move (in a tile-locked game)
  • Vector axis controls (vec2, -1.0 to 1.0 in each component), composed from 4 axes, one for each cardinal direction
    • RS
    • Mouselook
    • For e.g. look (in a first-person game)
    • For e.g. move (in a top-down game)
  • Repeating controls (event), composed from a button or axis
    • For e.g. holding up in menu navigation continuing to move the selection up

The mouse and text input are forms of input that cannot easily be abstracted.

Some actions are derivative from other actions, such when the jump button is pressed the player character should preform a jump, but it should be a larger jump if it is held. This is a case where two different types of actions (such as a button and events) must be bound to the same input source at all times.

Current idea for input in psuedocode, using the menu-options-pause example from State Machines with extra gameplay states:

event confirm = KEY_ENTER,     KEY_C, CONTROLLER_A
event back    = KEY_BACKSPACE, KEY_X, CONTROLLER_B
event pause   = KEY_ESCAPE,           CONTROLLER_START
event attack  =                KEY_X, CONTROLLER_B
event up      = KEY_UP,               CONTROLLER_LS_Y_PLUS,  CONTROLLER_DPAD_UP
event down    = KEY_DOWN,             CONTROLLER_LS_Y_MINUS, CONTROLLER_DPAD_DOWN

button jump   =                KEY_C, CONTROLLER_A
event doJump = jump.pressed

fullaxis move = (KEY_LEFT, KEY_RIGHT),
                (CONTROLLER_LS_X_MINUS, CONTROLLER_LS_X_PLUS),
                (CONTROLLER_DPAD_LEFT, CONTROLLER_DPAD_RIGHT)

context Menu.{Main, Options, Pause} {
    confirm,
    back,
    up,
    down
}

context Menu.Gameplay {
    pause

    context Menu.Gameplay.context.Dialog {
        confirm
    }

    context Menu.Gameplay.context.Normal {
        jump,
        move,
        attack
    }
}

Note that in this case, Menu is the main state machine instance. Also, the context stuff might get moved to or integrated with Events

Entity Component Model

TODO / Notes

  • Entities are made entirely of components
  • No entity is more or less "special" than any other entity
  • Entities with a component can be queried
  • Components are pure data and can only have one instance of a component attached at once
  • Switch components (name subject to change), like normal components, can only have one instance attached to an entity but the actual type of the component can be any of its variants. When used, the actual type of the switch component is pattern matched and different code is executed using that information. Example use case: AI controllers, where having more than one does not make sense and is almost certainly a bug
  • State Machine components (used as sum types, with pattern matching and whatnot)
  • Component traits are interfaces that can be used to provide encapsulated access to many components that share some common attribute. Example use case: status effects are separate components but have a trait that can provide the rendering system a graphic and duration so that a list of them can be displayed
    • I might want to make a component trait implementable by a combination of components, but I don't currently have a use case for this.
  • Components on an entity can be queried with the entity handle and the component type (including component traits, which results in a list of components)
  • All components are double-buffered; they have old and new states (old state should probably be accessible through a special previous (name subject to change) field)
  • Entity archetypes (constructors) for easy construction of common entity "classes"
  • Components might want to allow default values to reduce unneeded complexity

Concept psuedocode:

component Position(Vec2)
component Velocity(Vec2)

component Controller {
    move: Float = 0.0f,
    jumping: Boolean = false,
    attacking: Boolean = false
}

switch component Graphical {
	Image(ImageResource)

	Animation {
        resource: AnimationResource,
        frame: Int = 0
    }
}

machine component PlatformingState {
    state Ground {
        surface: Entity[Position],
        normal: Vec2
    }
    
    state Jumping {
        timer: Int
    }
    
    default state Falling
}

component DamageReduction {
    percentage: Float
    timer: Int
}

component Regeneration {
    amount: Float
    timer: Int
}

component trait StatusEffect {
    graphic: ImageResource
    remainingTime: Int
}

StatusEffect for DamageReduction {
    graphic = Resources.damageReductionImage,
    remainingTime = timer
}

StatusEffect for Regeneration {
    graphic = Resources.regenerationImage,
    remainingTime = timer
}

archetype Player {
    Position,
    Velocity,
    Controller,
    PlatformingState,
    Animation {
        resource = Resources.PlayerFallingAnimation
    }
}

Systems

TODO / Notes

  • Systems process entities with certain components
  • For the purpose of concurrent execution, systems annotate which components of an entity they access/modify/add/remove as well as which events they raise
  • Some systems (e.g. physics) need to have current and mutable access to multiple entities at the same time.
  • Rendering systems require access to the interpolation factor (intrinsic identifier, maybe called alpha?)
  • Systems need to have a defined order of execution
  • When a system operates on a trait component, it gets a list of components that implement it
  • Iterating on entities with a set of components should be first-class

Current concept psuedocode, using components from the Entity Component Model psuedocode:

entity system statusTimers(mut StatusEffect (effects)) {
	for statusEffect in effects {
        statusEffect.remainingTime -= 1;
        if statusEffect.remainingTime <= 0 {
            remove StatusEffect
        }
    }
}

entity system regeneration(mut Health, Regeneration) {
    health += regeneration.amount;
}

entity system animationTimers(mut Animation) {
    animation.animationFrame += 1;
    animation.animationFrame %= animation.resource.totalFrames;
}

render entity system imageRender(Position, Image) {
    vertexBuffer.put(
        lerp(position.previous, position, alpha),
        image.texcoords
    );
}

Events

TODO / Notes

Possible Future Features

...in no particular order

  • Resource Handling
  • Graphics Pipeline
  • Audio Stuff
  • UI Toolkit
  • TAS (Tool-Assisted Superplay) Tools
    • Random number generation must be manipulable to ensure determinism
    • Save state (serialization)
  • Input Gestures (e.g. Down+Left to use bomb in Crypt of the NecroDancer)
  • Dialog Trees
  • Game Saving (serialization)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment