Package tripleplay.demo.entity

Source Code of tripleplay.demo.entity.AsteroidsDemo

//
// Triple Play - utilities for use in PlayN-based games
// Copyright (c) 2011-2014, Three Rings Design, Inc. - All rights reserved.
// http://github.com/threerings/tripleplay/blob/master/LICENSE

package tripleplay.demo.entity;

import java.util.Iterator;
import java.util.Random;

import playn.core.*;
import playn.core.util.Clock;
import static playn.core.PlayN.*;

import pythagoras.f.FloatMath;
import pythagoras.f.MathUtil;
import pythagoras.f.Point;
import pythagoras.f.Vector;

import react.Signal;
import react.Slot;

import tripleplay.ui.Group;
import tripleplay.ui.layout.AxisLayout;
import tripleplay.util.Randoms;
import tripleplay.util.StyledText;
import tripleplay.util.TextStyle;

import tripleplay.entity.Component;
import tripleplay.entity.Entity;
import tripleplay.entity.System;
import tripleplay.entity.World;

import tripleplay.demo.DemoScreen;

public class AsteroidsDemo extends DemoScreen
{
    public final Image asteroids = assets().getImage("images/asteroids.png");

    enum Size {
        TINY(20), SMALL(40), MEDIUM(60), LARGE(80);
        public final int size;
        Size (int size) { this.size = size; }
    }

    class AsteroidsWorld extends World {
        public final GroupLayer stage;
        public final float swidth, sheight;
        public final Randoms rando = Randoms.with(new Random());

        public static final int SHIP     = (1 << 0);
        public static final int ASTEROID = (1 << 1);
        public static final int BULLET   = (1 << 2);

        public final Component.IMask type = new Component.IMask(this);
        public final Component.XY opos = new Component.XY(this);
        public final Component.XY pos = new Component.XY(this);
        public final Component.XY vel = new Component.XY(this); // pixels/ms
        public final Component.Generic<Layer> sprite = new Component.Generic<Layer>(this);
        public final Component.Generic<Size> size = new Component.Generic<Size>(this);
        public final Component.FScalar spin = new Component.FScalar(this); // rads/ms
        public final Component.FScalar radius = new Component.FScalar(this);
        public final Component.IScalar expires = new Component.IScalar(this);

        public final Signal<Key> keyDown = Signal.create();
        public final Signal<Key> keyUp = Signal.create();

        public int now; // ms elapsed since world start, used by expirer/expires

        // handles updating entity position based on entity velocity
        public final System mover = new System(this, 0) {
            @Override protected void update (int delta, Entities entities) {
                Point p = _pos;
                Vector v = _vel;
                for (int ii = 0, ll = entities.size(); ii < ll; ii++) {
                    int eid = entities.get(ii);
                    pos.get(eid, p); // get our current pos
                    p.x = wrapx(p.x); // wrap it around the screen if necessary
                    p.y = wrapy(p.y);
                    opos.set(eid, p); // copy wrapped pos to opos
                    vel.get(eid, v).scaleLocal(delta); // turn velocity into delta pos
                    pos.set(eid, p.x + v.x, p.y + v.y); // add velocity (but don't wrap)
                }
            }

            @Override protected boolean isInterested (Entity entity) {
                return entity.has(opos) && entity.has(pos) && entity.has(vel);
            }

            protected final Point _pos = new Point();
            protected final Vector _vel = new Vector();
        };

        private float wrapx (float x) {
            return (x > swidth) ? (x-swidth) : ((x < 0) ? (x+swidth) : x);
        }
        private float wrapy (float y) {
            return (y > sheight) ? (y-sheight) : ((y < 0) ? (y+sheight) : y);
        }

        // updates sprites to interpolated position of entities on each paint() call
        public final System spriter = new System(this, 0) {
            @Override protected void paint (Clock clock, Entities entities) {
                float alpha = clock.alpha();
                Point op = _oldPos, p = _pos;
                for (int ii = 0, ll = entities.size(); ii < ll; ii++) {
                    int eid = entities.get(ii);
                    // interpolate between opos and pos and use that to update the sprite position
                    opos.get(eid, op);
                    pos.get(eid, p);
                    // wrap our interpolated position as we may interpolate off the screen
                    sprite.get(eid).setTranslation(wrapx(MathUtil.lerp(op.x, p.x, alpha)),
                                                   wrapy(MathUtil.lerp(op.y, p.y, alpha)));
                }
            }

            @Override protected void wasAdded (Entity entity) {
                super.wasAdded(entity);
                stage.addAt(sprite.get(entity.id), pos.getX(entity.id), pos.getX(entity.id));
            }

            @Override protected void wasRemoved (Entity entity, int index) {
                super.wasRemoved(entity, index);
                stage.remove(sprite.get(entity.id));
            }

            @Override protected boolean isInterested (Entity entity) {
                return entity.has(opos) && entity.has(pos) && entity.has(sprite);
            }

            protected final Point _oldPos = new Point(), _pos = new Point();
        };

        // spins things
        public final System spinner = new System(this, 0) {
            @Override protected void paint (Clock clock, Entities entities) {
                float dt = clock.dt();
                for (int ii = 0, ll = entities.size(); ii < ll; ii++) {
                    int eid = entities.get(ii);
                    float angvel = spin.get(eid);
                    if (angvel == 0) continue;
                    Layer s = sprite.get(eid);
                    s.setRotation(s.rotation() + angvel * dt);
                }
            }

            @Override protected boolean isInterested (Entity entity) {
                return entity.has(spin) && entity.has(sprite);
            }
        };

        // expires things with limited lifespan (like bullets)
        public final System expirer = new System(this, 0) {
            @Override protected void update (int delta, Entities entities) {
                int now = AsteroidsWorld.this.now;
                for (int ii = 0, ll = entities.size(); ii < ll; ii++) {
                    int eid = entities.get(ii);
                    if (expires.get(eid) <= now) world.entity(eid).destroy();
                }
            }

            @Override protected boolean isInterested (Entity entity) {
                return entity.has(expires);
            }
        };

        // checks for collisions (modeling everything as a sphere)
        public final System collider = new System(this, 1) {
            @Override protected void update (int delta, Entities entities) {
                // simple O(n^2) collision check; no need for anything fancy here
                for (int ii = 0, ll = entities.size(); ii < ll; ii++) {
                    int eid1 = entities.get(ii);
                    Entity e1 = world.entity(eid1);
                    if (e1.isDestroyed()) continue;
                    pos.get(eid1, _p1);
                    float r1 = radius.get(eid1);
                    for (int jj = ii+1; jj < ll; jj++) {
                        int eid2 = entities.get(jj);
                        Entity e2 = world.entity(eid2);
                        if (e2.isDestroyed()) continue;
                        pos.get(eid2, _p2);
                        float r2 = radius.get(eid2), dr = r2+r1;
                        float dist2 = _p1.distanceSq(_p2);
                        if (dist2 <= dr*dr) {
                            collide(e1, e2);
                            break; // don't collide e1 with any other entities
                        }
                    }
                }
            }

            @Override protected boolean isInterested (Entity entity) {
                return entity.has(pos) && entity.has(radius);
            }

            private void collide (Entity e1, Entity e2) {
                switch (type.get(e1.id) | type.get(e2.id)) {
                case SHIP_ASTEROID:
                    explode(type.get(e1.id) == SHIP ? e1 : e2, 10, 0.75f);
                    setMessage("Game Over. Press 's' to restart");
                    _wave = -1;
                    break;
                case BULLET_ASTEROID:
                    if (type.get(e1.id) == ASTEROID) {
                        sunder(e1);
                        e2.destroy();
                    } else {
                        sunder(e2);
                        e1.destroy();
                    }
                    break;
                // TODO: asteroid asteroid?
                default: break; // nada
                }
            }

            protected static final int SHIP_ASTEROID = SHIP|ASTEROID;
            protected static final int BULLET_ASTEROID = BULLET|ASTEROID;

            protected final Point _p1 = new Point(), _p2 = new Point();
        };

        // handles progression to next wave
        public final System waver = new System(this, 0) {
            @Override protected void update (int delta, Entities entities) {
                // if the only entity left is the player's ship; move to the next wave
                if (entities.size() == 1 && type.get(entities.get(0)) == SHIP) {
                    startWave(++_wave);
                }
            }

            @Override protected boolean isInterested (Entity entity) {
                return true;
            }
        };

        // handles player input
        public final System controls = new System(this, 1) {
            public static final float ACCEL = 0.01f;
            public static final float ROT = 0.005f;
            public static final float MAX_VEL = 1f;
            public static final int   BULLET_LIFE = 1000; // ms
            public static final float BULLET_VEL = 0.25f;

            /* ctor */ {
                keyDown.connect(new Slot<Key>() {
                    @Override public void onEmit (Key key) {
                        switch (key) {
                        case LEFT:  _angvel = -ROT;   break;
                        case RIGHT: _angvel =  ROT;   break;
                        case UP:    _accel  =  ACCEL; break;
                        case SPACE: if (_wave >=  0) fireBullet(); break;
                        case S:     if (_wave == -1) startWave(0); break;
                        default: break;
                        }
                    }
                });
                keyUp.connect(new Slot<Key>() {
                    @Override public void onEmit (Key key) {
                        switch (key) {
                        case LEFT:  _angvel = 0; break;
                        case RIGHT: _angvel = 0; break;
                        case UP:    _accel  = 0; break;
                        default: break;
                        }
                    }
                });
            }

            public void fireBullet () {
                int sid = _ship.id;
                float ang = sprite.get(sid).rotation();
                float vx = vel.getX(sid), vy = vel.getY(sid);
                float bvx = vx+BULLET_VEL*FloatMath.cos(ang), bvy = vy+BULLET_VEL*FloatMath.sin(ang);
                createBullet(pos.getX(sid), pos.getY(sid), bvx, bvy, ang, now + BULLET_LIFE);
                vel.set(sid, vx-bvx/100, vy-bvy/100); // decrease ship's velocity a smidgen
            }

            @Override protected void update (int delta, Entities entities) {
                Vector v = _vel;
                for (int ii = 0, ll = entities.size(); ii < ll; ii++) {
                    int eid = entities.get(ii);
                    spin.set(eid, _angvel);
                    if (_accel != 0) {
                        Layer s = sprite.get(eid);
                        float ang = s.rotation();
                        vel.get(eid, v);
                        v.x = MathUtil.clamp(v.x + FloatMath.cos(ang)*_accel, -MAX_VEL, MAX_VEL);
                        v.y = MathUtil.clamp(v.y + FloatMath.sin(ang)*_accel, -MAX_VEL, MAX_VEL);
                        vel.set(eid, v);
                    }
                }
            }

            @Override protected void wasAdded (Entity entity) {
                super.wasAdded(entity);
                _ship = entity;
            }

            @Override protected boolean isInterested (Entity entity) {
                return type.get(entity.id) == SHIP;
            }

            protected Vector _vel = new Vector();
            protected float _angvel, _accel;
            protected Entity _ship;
        };

        public AsteroidsWorld (GroupLayer stage, float swidth, float sheight) {
            this.stage = stage;
            this.swidth = swidth;
            this.sheight = sheight;

            keyboard().setListener(new Keyboard.Adapter() {
                @Override public void onKeyDown (Keyboard.Event event) {
                    keyDown.emit(event.key());
                }
                @Override public void onKeyUp (Keyboard.Event event) {
                    keyUp.emit(event.key());
                }
            });
        }

        public void setMessage (String text) {
            if (_msg != null) _msg.destroy();
            if (text != null) {
                _msg = StyledText.span(text, MSG_STYLE).toLayer();
                _msg.setDepth(1);
                stage.addAt(_msg, (swidth-_msg.width())/2, (sheight-_msg.height())/2);
            }
        }

        public void attract () {
            for (int ii = 0; ii < 5; ii++) createAsteroid(
                Size.LARGE, rando.getFloat(swidth), rando.getFloat(sheight));
            setMessage("Press 's' to start.");
            _wave = -1;
        }

        public void startWave (int wave) {
            // if this is wave 0, destroy any existing entities and add our ship
            if (wave == 0) {
                Iterator<Entity> iter = entities();
                while (iter.hasNext()) iter.next().destroy();
                createShip(swidth/2, sheight/2);
                setMessage(null);
            }
            for (int ii = 0, ll = Math.min(10, wave+2); ii < ll; ii++) {
                float x = rando.getFloat(swidth), y = rando.getFloat(sheight);
                // TODO: make sure x/y doesn't overlap ship
                createAsteroid(Size.LARGE, x, y);
            }
            _wave = wave;
        }

        public void explode (Entity target, int frags, float maxvel) {
            float x = pos.getX(target.id), y = pos.getY(target.id);
            // create a bunch of bullets going in random directions from the ship
            for (int ii = 0; ii < frags; ii++) {
                float ang = rando.getInRange(-FloatMath.PI, FloatMath.PI);
                float vel = rando.getInRange(maxvel/3, maxvel);
                float vx = FloatMath.cos(ang)*vel, vy = FloatMath.sin(ang)*vel;
                createBullet(x, y, vx, vy, ang, now + 300/*ms*/);
            }
            // and destroy the target
            target.destroy();
        }

        @Override public void update (int delta) {
            now += delta;
            super.update(delta);
        }

        protected String typeName (int id) {
            switch (type.get(id)) {
            case     SHIP: return "ship";
            case   BULLET: return "bullet";
            case ASTEROID: return "asteroid";
            default:       return "unknown:" + type.get(id);
            }
        }

        protected String toString (int id) {
            return typeName(id) + ":" + id + "@" + pos.getX(id) + "/" + pos.getY(id);
        }

        protected Entity createShip (float x, float y) {
            Entity ship = create(true);
            ship.add(type, sprite, opos, pos, vel, spin, radius);

            CanvasImage bitmap = graphics().createImage(30, 20);
            Path path = bitmap.canvas().createPath();
            path.moveTo(0, 0).lineTo(30, 10).lineTo(0, 20).close();
            bitmap.canvas().setFillColor(0xFFCC99FF).fillPath(path);
            ImageLayer layer = graphics().createImageLayer(bitmap);
            layer.setOrigin(15, 10);
            layer.setRotation(-MathUtil.HALF_PI);

            int id = ship.id;
            type.set(id, SHIP);
            sprite.set(id, layer);
            opos.set(id, x, y);
            pos.set(id, x, y);
            vel.set(id, 0, 0);
            radius.set(id, 10);
            return ship;
        }

        protected static final float MAXVEL = 0.02f;
        protected static final float MAXSPIN = 0.001f;

        protected Entity createAsteroid (Size size, float x, float y) {
            return createAsteroid(size, x, y, rando.getInRange(-MAXVEL, MAXVEL),
                                  rando.getInRange(-MAXVEL, MAXVEL));
        }

        protected Entity createAsteroid (Size sz, float x, float y, float vx, float vy) {
            Entity ast = create(true);
            ast.add(type, size, sprite, opos, pos, vel, spin, radius);

            float side = sz.size;
            int iidx = rando.getInt(8);
            float ah = asteroids.height();
            ImageLayer layer = graphics().createImageLayer(asteroids.subImage(iidx*ah, 0, ah, ah));
            layer.setOrigin(ah/2, ah/2);
            layer.setScale(side/ah);
            layer.setRotation(rando.getFloat(MathUtil.TAU));

            int id = ast.id;
            type.set(id, ASTEROID);
            size.set(id, sz);
            sprite.set(id, layer);
            spin.set(id, rando.getInRange(-MAXSPIN, MAXSPIN));
            opos.set(id, x, y);
            pos.set(id, x, y);
            vel.set(id, vx, vy);
            radius.set(id, side*0.425f);
            return ast;
        }

        protected Entity createBullet (float x, float y, float vx, float vy, float angle, int exps) {
            Entity bullet = create(true);
            bullet.add(type, sprite, opos, pos, vel, radius, expires);

            CanvasImage bitmap = graphics().createImage(5, 2);
            bitmap.canvas().setFillColor(0xFFFFFFFF).fillRect(0, 0, 5, 2);
            ImageLayer layer = graphics().createImageLayer(bitmap);
            layer.setOrigin(2.5f, 1);
            layer.setRotation(angle);

            int id = bullet.id;
            type.set(id, BULLET);
            sprite.set(id, layer);
            opos.set(id, x, y);
            pos.set(id, x, y);
            vel.set(id, vx, vy);
            radius.set(id, 2);
            expires.set(id, exps);
            return bullet;
        }

        protected void sunder (Entity ast) {
            Size smaller;
            switch (size.get(ast.id)) {
            default:
            case TINY: explode(ast, 4, 0.25f); return;
            case SMALL: smaller = Size.TINY; break;
            case MEDIUM: smaller = Size.SMALL; break;
            case LARGE: smaller = Size.MEDIUM; break;
            }
            float x = pos.getX(ast.id), y = pos.getY(ast.id);
            float vx = vel.getX(ast.id), vy = vel.getY(ast.id);
            // break the asteroid into two pieces, spinning in opposite directions and headed at
            // roughly right angles to the original
            createAsteroid(smaller, x, y, -vy, vx);
            createAsteroid(smaller, x, y, vy, -vx);
            ast.destroy(); // and destroy ourself
        }

        protected int _wave = -1;
        protected ImageLayer _msg;
    }

    @Override public void wasRemoved () {
        super.wasRemoved();
        _world = null;
        keyboard().setListener(null);
    }

    @Override public void update (int delta) {
        super.update(delta);
        if (_world != null) _world.update(delta);
    }

    @Override public void paint (Clock clock) {
        super.paint(clock);
        if (_world != null) _world.paint(clock);
    }

    @Override protected String name () {
        return "Asteroids";
    }

    @Override protected String title () {
        return "Asteroids Demo";
    }

    @Override protected Group createIface () {
        return new Group(AxisLayout.vertical()) {
            @Override protected void layout () {
                super.layout();
                if (_world == null) {
                    _world = new AsteroidsWorld(layer, size().width(), size().height());
                    _world.attract();
                }
            }
        };
    }

    protected AsteroidsWorld _world;
    protected Group _group;

    protected static final TextStyle MSG_STYLE = new TextStyle().withTextColor(0xFFFFFFFF).
        withFont(graphics().createFont("Helvetica", Font.Style.PLAIN, 24));
}
TOP

Related Classes of tripleplay.demo.entity.AsteroidsDemo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.