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;
}
@Gioele-Bencivenga
Copy link

I'd like to use Echo with HaxeFlixel in a project so I tried to get this working.

I created a new HaxeFlixel project, added a folder "states" and a folder "util" in my project directory.
I put PlayState.hx in states, FlxEcho.hx in util. Both files are identical to these 2 files in this gist.
I added in my Project.xml.

In FlxEcho at line 67 I'm getting the error Float has no field round coming from dot(Vector2.yAxis).round()
In FlxEcho.hx at line 77 I'm getting the error flixel.FlxBasic has no field is coming from a.is and b.is
In FlxEcho at line 112 I'm getting the error Float has no field abs coming from dot_x.abs()
In PlayState at line 89 I'm getting the error Float has no field round coming from dot(Vector2.yAxis).round()

I apologize if the solution is straighforward but I cannot see it, thanks in advance for any help!

@01010111
Copy link
Author

01010111 commented May 1, 2020

I'd like to use Echo with HaxeFlixel in a project so I tried to get this working.

I created a new HaxeFlixel project, added a folder "states" and a folder "util" in my project directory.
I put PlayState.hx in states, FlxEcho.hx in util. Both files are identical to these 2 files in this gist.
I added in my Project.xml.

In FlxEcho at line 67 I'm getting the error Float has no field round coming from dot(Vector2.yAxis).round()
In FlxEcho.hx at line 77 I'm getting the error flixel.FlxBasic has no field is coming from a.is and b.is
In FlxEcho at line 112 I'm getting the error Float has no field abs coming from dot_x.abs()
In PlayState at line 89 I'm getting the error Float has no field round coming from dot(Vector2.yAxis).round()

I apologize if the solution is straighforward but I cannot see it, thanks in advance for any help!

No problem! I think I probably had an import.hx with some things in it. In FlxEcho add:

using Math;
using Std;

and in PlayState add:

using Math;

and it should get rid of those errors!

@Gioele-Bencivenga
Copy link

I'd like to use Echo with HaxeFlixel in a project so I tried to get this working.
I created a new HaxeFlixel project, added a folder "states" and a folder "util" in my project directory.
I put PlayState.hx in states, FlxEcho.hx in util. Both files are identical to these 2 files in this gist.
I added in my Project.xml.
In FlxEcho at line 67 I'm getting the error Float has no field round coming from dot(Vector2.yAxis).round()
In FlxEcho.hx at line 77 I'm getting the error flixel.FlxBasic has no field is coming from a.is and b.is
In FlxEcho at line 112 I'm getting the error Float has no field abs coming from dot_x.abs()
In PlayState at line 89 I'm getting the error Float has no field round coming from dot(Vector2.yAxis).round()
I apologize if the solution is straighforward but I cannot see it, thanks in advance for any help!

No problem! I think I probably had an import.hx with some things in it. In FlxEcho add:

using Math;
using Std;

and in PlayState add:

using Math;

and it should get rid of those errors!

Worked like a charm, thanks a lot for the quick reply!

@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