Skip to content

Instantly share code, notes, and snippets.

@01010111
Last active April 29, 2021 17:25
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save 01010111/3370831f78beae4aad768591a8eeabdb to your computer and use it in GitHub Desktop.
Save 01010111/3370831f78beae4aad768591a8eeabdb to your computer and use it in GitHub Desktop.
Quick Flixel <-> Echo integration

IMPORTANT!

This is old! I think it probably works, but there's a new, better way to integrate Echo with Flixel!

Check it out: echo-flixel

package util;
import echo.data.Options.BodyOptions;
import flixel.FlxBasic;
import flixel.FlxObject;
import flixel.FlxObject.*;
import flixel.group.FlxGroup;
import echo.Echo;
import echo.World;
import echo.Body;
import echo.data.Options.ListenerOptions;
import echo.data.Options.WorldOptions;
using hxmath.math.Vector2;
class FlxEcho {
static var groups:Map<FlxGroup, Array<Body>>;
static var bodies:Map<FlxObject, Body>;
static var world:World;
/**
* Init the world
*/
public static function init(options:WorldOptions) {
groups = [];
bodies = [];
world = Echo.start(options);
}
/**
* add physics body to FlxObject
*/
public static function add_body(object:FlxObject, ?options:BodyOptions) {
if (options == null) options = {};
if (options.x == null) options.x = object.x;
if (options.y == null) options.y = object.y;
if (options.shape == null) options.shape = {
type: RECT,
width: object.width,
height: object.height,
offset_x: object.width/2,
offset_y: object.height/2
}
var body = new Body(options);
bodies.set(object, body);
world.add(body);
}
/**
* Adds FlxObject to FlxGroup, and the FlxObject's associated physics body to the FlxGroup's associated physics group
*/
public static function add_to_group(object:FlxObject, group:FlxGroup) {
group.add(object);
if (!groups.exists(group)) groups.set(group, []);
if (bodies.exists(object)) groups[group].push(bodies[object]);
}
/**
* Creates a physics listener
*/
public static function listen(a:FlxBasic, b:FlxBasic, ?options:ListenerOptions) {
if (options == null) options = {};
var temp_stay = options.stay;
options.stay = (a, b, c) -> {
if (temp_stay != null) temp_stay(a, b, c);
for (col in c) set_touching(get_object(a), [CEILING, WALL, FLOOR][col.normal.dot(Vector2.yAxis).round() + 1]);
}
#if ARCADE_PHYSICS
var temp_condition = options.condition;
options.condition = (a, b, c) -> {
for (col in c) square_normal(col.normal);
if (temp_condition != null) return temp_condition(a, b, c);
return true;
}
#end
world.listen(!a.is(FlxObject) ? groups[cast a] : bodies[cast a], !b.is(FlxObject) ? groups[cast b] : bodies[cast b], options);
}
/**
* Update physics and objects with physics bodies
*/
public static function update(elapsed:Float) {
world.step(elapsed);
for (object => body in bodies) update_body_object(object, body);
}
/**
* Get the physics body associated with a FlxObject
*/
public static function get_body(object:FlxObject):Body return bodies[object];
/**
* Get the FlxObject associated with a physics body
*/
public static function get_object(body:Body):FlxObject {
for (o => b in bodies) if (b == body) return o;
return null;
}
static function update_body_object(object:FlxObject, body:Body) {
object.setPosition(body.x, body.y);
object.angle = body.rotation;
}
static function set_touching(object:FlxObject, touching:Int) if (object.touching & touching == 0) object.touching += touching;
static function square_normal(normal:Vector2) {
var len = normal.length;
var dot_x = normal.dot(Vector2.xAxis);
var dot_y = normal.dot(Vector2.yAxis);
if (dot_x.abs() > dot_y.abs()) dot_x > 0 ? normal.set(1, 0) : normal.set(-1, 0);
else dot_y > 0 ? normal.set(0, 1) : normal.set(0, -1);
normal.normalizeTo(len);
}
}
package states;
import flixel.FlxState;
import flixel.FlxSprite;
import flixel.FlxObject.*;
import flixel.group.FlxGroup;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
using util.FlxEcho;
using hxmath.math.Vector2;
using flixel.util.FlxSpriteUtil;
class PlayState extends FlxState
{
var player:Box;
var level_data = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
];
override function create() {
// First thing we want to do before creating any physics objects is init() our Echo world.
FlxEcho.init({
width: level_data[0].length * 16, // Make the size of your Echo world equal the size of your play field
height: level_data.length * 16,
gravity_y: 800
});
// Normal, every day FlxGroups!
var terrain = new FlxGroup();
add(terrain);
var bouncers = new FlxGroup();
add(bouncers);
// We'll step through our level data and add objects that way
for (j in 0...level_data.length) for (i in 0...level_data[j].length) {
switch (level_data[j][i]) {
case 1:
// Just a regular old terrain block
var bluebox = new Box(i * 16, j * 16, 16, 16, 0xFF0080FF);
bluebox.add_body({ mass: 0 }); // We'll pass in body options with mass set to 0 so that it's static
bluebox.add_to_group(terrain); // Instead of `group.add(object)` we use `object.add_to_group(group)`
case 2:
// Orange boxes will act like springs!
var orangebox = new Box(i * 16, j * 16, 16, 16, 0xFFFF8000);
orangebox.origin.set(8, 16); // We'll set the origin here so that we can animate our orange block later
orangebox.add_body({ mass: 0 });
orangebox.add_to_group(bouncers);
case 3:
player = new Box(i * 16, j * 16, 8, 12, 0xFFFF004D, true);
player.add_body();
add(player);
default: continue;
}
}
// lets add some ramps too! They'll belong to the same collision group as the blue boxes we made earlier.
for (i in 0...8) {
var ramp = new Ramp(16, 112 + i * 16, 16 + i * 16, 128 - i * 16, NW);
ramp.add_to_group(terrain);
}
// Our first physics listener collides our player with the terrain group.
player.listen(terrain);
// Our second physics listener collides our player with the bouncers group.
player.listen(bouncers, {
// We'll add this listener option - every frame our player object is colliding with a bouncer in the bouncers group we'll run this function
stay: (a, b, c) -> { // where a is our first physics body (`player`), b is the physics body it's colliding with (`orangebox`), and c is an array of collision data.
// for every instance of collision data
for (col in c) {
// This checks to see if the normal of our collision is pointing downward - you could use it for hop and bop games to see if a player has stomped on an enemy!
if (col.normal.dot(Vector2.yAxis).round() == 1) {
// set the player's velocity to go up!
a.velocity.y = -400;
// animate the orange box!
var b_object:FlxSprite = cast b.get_object();
b_object.scale.y = 1.5;
FlxTween.tween(b_object.scale, { y: 1 }, 0.5, { ease: FlxEase.elasticOut });
}
}
}
});
}
override function update(e:Float) {
// Make sure to call `FlxEcho.update()` before `super.update()`!
FlxEcho.update(e);
super.update(e);
}
}
class Box extends FlxSprite {
var control:Bool;
public function new(x:Float, y:Float, w:Int, h:Int, c:Int, control:Bool = false) {
super(x, y);
makeGraphic(w, h, c);
this.control = control;
}
override function update(elapsed:Float) {
if (control) controls();
super.update(elapsed);
}
function controls() {
var body = this.get_body();
body.velocity.x = 0;
if (FlxG.keys.pressed.LEFT) body.velocity.x -= 128;
if (FlxG.keys.pressed.RIGHT) body.velocity.x += 128;
if (FlxG.keys.justPressed.UP && isTouching(FLOOR)) body.velocity.y -= 256;
}
}
class Ramp extends FlxSprite {
public function new(x:Float, y:Float, w:Int, h:Int, d:RampDirection) {
trace('$x / $y / $w / $h');
super(x, y);
makeGraphic(w, h, 0x00FFFFFF);
var verts = [ [0, 0], [w, 0], [w, h], [0, h] ];
switch d {
case NE: verts.splice(0, 1);
case NW: verts.splice(1, 1);
case SE: verts.splice(3, 1);
case SW: verts.splice(2, 1);
}
this.drawPolygon([ for (v in verts) FlxPoint.get(v[0], v[1]) ], 0xFFFF0080);
this.add_body({
mass: 0,
shape: {
type: POLYGON,
vertices: [ for (v in verts) new Vector2(v[0], v[1]) ],
}
});
}
}
enum RampDirection {
NE;
NW;
SE;
SW;
}
@MrTambourineSLO
Copy link

First, thanks a lot for this piece of code, good work! I'd like to add, though that it has a rather nasty physics glitch still, namely if you jump in a corner and keep pressing towards the wall the "player" will stick to the wall, unaffected by gravity as in picture below. Still, goof work 👍
image

@01010111
Copy link
Author

First, thanks a lot for this piece of code, good work! I'd like to add, though that it has a rather nasty physics glitch still, namely if you jump in a corner and keep pressing towards the wall the "player" will stick to the wall, unaffected by gravity as in picture below. Still, goof work 👍
image

Yeah! So Echo can help with that too! Instead of just making the blue boxes individual bodies, you'd create a TileMap which iirc simplifies individual "boxes" into bigger rectangles!

@MrTambourineSLO
Copy link

MrTambourineSLO commented Sep 13, 2020

I'm actually experimenting with this rn. Thanks for the heads up!
Edit: yup, tilemap util walks flawlessly with collisions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment