Giter Site home page Giter Site logo

passiomatic / sunny-land Goto Github PK

View Code? Open in Web Editor NEW
125.0 7.0 7.0 302 KB

A WebGL Elm Playground platformer.

Home Page: https://lab.passiomatic.com/sunny-land/

License: BSD 3-Clause "New" or "Revised" License

PLSQL 18.90% Elm 80.97% Makefile 0.13%
elm webgl physics-engine

sunny-land's Introduction

Sunny Land โ€” A WebGL Playground platformer

Sunny Land is both a learning experiment and ongoing project to showcase Elm's WebGL Playground package potential.

PLAY ONLINE

Sunny Land level

Goals

It aims to have a fairly complete feature set found in typical 80's platformers:

  • A simple 2d physics engine
  • Different game entities: player, enemies and bonus items
  • Whole screen graphic FX
  • Enemy respawn logic
  • Game over logic
  • Multiple levels (to-do)

Run locally

Clone the repo and on the command line run Elm's reactor:

elm reactor

Then point your browser to http://localhost:8000/src/Main.elm

How this thing works

Initialisation

Depending on Memory.status value an intro screen or a game level is shown.

After the intro screen and before playing (and for every level change) entities are spawned using information found in Level.spawns and assigned to Memory.entities dictionary. When entities are ready the game loop can run.

One useful trick is to assign a know value (zero) to the player entity ID, so it can be easily retrieved later.

Game loop

At each frame the Main.update function is executed the following steps are performed:

  1. All the game entities are updated. In particular, during this step user input is captured and accelaration for the NPC's (non-player characters) is set.
  2. The physics simulation is run to figure out the next positions for all the entities taking any contacts into account.
  3. The list of contacts resulting from step (2) is finally used to perform any game logic.

Physics engine

The physics code has been adapted from the particle engine found in Game Physics Engine Development book by Ian Millington. It handles contact detection and resolution between circles and circles against line segments.

To handle contacts each physics body (game entity) has an associated circle shape (defined by radius), while ground and walls are defined by line segments.

The Physics module knows very little about the game itself, so it can be easily extracted and used on another project. Strictly speaking a physics body needs only a subset of Entity fields, hence it is defined as an extensible record:

type alias PhysicsBody a =
    { a
        | id : Int
        , p : Vec2
        , v : Vec2
        , a : Vec2
        , radius : Float
        , restitution : Float
        , contact : Vec2
        , contactTestBitMask : Int
        , categoryBitMask : Int
        , affectedByGravity : Bool
        , affectedByContact : Bool
    }

The entry point of the physics engine is the Physics.step function. At each frame the step function is called accepting the current level walls and physics bodies and it takes care of:

  1. Apply forces (gravity and ground friction) to each body, and update velocity accordingly.
  2. For each physics body figure out contacts with walls and other bodies, ignoring duplicated contacts. In other words, if there was a A-B contact there's no need to generate its twin B-A contact.
  3. Resolve contacts one after another, applying impulses to separate affected bodies and fixing interpenetrations.
  4. Returns to caller the updated bodies and a list of generated contacts.

Game logic handling

Each Contact is defined as a custom type:

type Contact a
    = BetweenBodies Int Int ContactData
    | WithWall Int ContactData

While looping through the contact list, WithWall values are discared while BetweenBodies are passed to Entity.respond function. This function figures out what to do next doing a case/of on Entity.type_ fields, i.e. when player hits an enemy the game needs to decide if the former stomped the latter or got hit:

respond id1 id2 contact memory =
    case ( Dict.get id1 memory.entities, Dict.get id2 memory.entities ) of
        ( Just entity1, Just entity2 ) ->
            case ( entity1.type_, entity2.type_ ) of
                ( Player, Opossum ) ->
                    -- Player touched Opossum enemy
                    ...

Level design

Game levels are designed using Tiled, saved to JSON files and then converted into Elm code via a Python script (see Levels.elm). You can open assets/level1.json with Tiled and see how the various level layers are used to define the terrain, the obstacles and spawn points for the game entities.

Credits

Game art by Ansimuz https://ansimuz.itch.io/sunny-land-pixel-game-art

sunny-land's People

Contributors

passiomatic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

sunny-land's Issues

Player is pulled up/aside when dealing with one-way platforms

This is due to the way I'm implementing the check to block (or not) the player while it jumps on a one-way platform from below.

Here (https://github.com/passiomatic/sunny-land/blob/main/src/Physics.elm#L284) I'm projecting the player velocity along the wall normal and see if they have the same direction. If this is the case the player is coming from behind/below the wall and the game doesn't block him.

This is a simple check and works well, but it has one quirk: if the player doesn't jump high enough to reach the platform he ends up halfway and the moment the velocity starts inverting its direction (because of gravity) a collision is generated and an impulse is calculated "fixing" his position abruptly.

Fix: use code from https://github.com/passiomatic/platformer-physics

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.