/**
*
* Copyright 2008 - 2010
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* @project loon
* @author cping
* @email:javachenpeng@yahoo.com
* @version 0.1
*/
package loon.component;
import loon.LObject;
import loon.LRelease;
import loon.LSystem;
import loon.LTexture;
import loon.LTextures;
import loon.PlayerUtils;
import loon.action.ActionBind;
import loon.action.ActionTween;
import loon.action.ArrowTo;
import loon.action.CircleTo;
import loon.action.ColorTo;
import loon.action.FadeTo;
import loon.action.FireTo;
import loon.action.JumpTo;
import loon.action.MoveTo;
import loon.action.RotateTo;
import loon.action.ScaleTo;
import loon.action.ShakeTo;
import loon.action.map.Field2D;
import loon.action.sprite.Animation;
import loon.canvas.LColor;
import loon.component.layout.BoxSize;
import loon.geom.RectBox;
import loon.geom.Vector2f;
import loon.geom.XY;
import loon.opengl.GLEx;
import loon.utils.Flip;
import loon.utils.MathUtils;
import loon.utils.TArray;
import loon.utils.timer.LTimer;
public class Actor extends LObject<Actor> implements Flip<Actor>,
Comparable<Actor>, ActionBind, XY, LRelease, BoxSize {
private String flag = "Actor";
private static int sequenceNumber = 0;
private int noSequenceNumber;
private int lastPaintSequenceNumber;
boolean visible = true, drag = true, click = true;
private ActorLayer gameLayer;
private LTexture image;
Object data;
private RectBox boundingRect;
private float[] xs = new float[4];
private float[] ys = new float[4];
private LTimer timer = new LTimer(0);
private Animation animation;
boolean isAnimation;
LColor filterColor;
ActorListener actorListener;
float scaleX = 1, scaleY = 1;
boolean flipX = false, flipY = false;
public Actor(Animation animation) {
this(animation, 0, 0);
}
public Actor(Animation animation, int x, int y) {
if (animation == null) {
throw LSystem.runThrow("Animation is null !");
}
this.noSequenceNumber = sequenceNumber++;
this.animation = animation;
this.isAnimation = true;
this._location.set(x, y);
this.setImage(animation.getSpriteImage());
}
public Actor() {
this((LTexture) null);
}
public Actor(LTexture image, int x, int y) {
this.noSequenceNumber = sequenceNumber++;
this._location.set(x, y);
this.setImage(image);
}
public Actor(LTexture image) {
this(image, 0, 0);
}
public Actor(String fileName, int x, int y) {
this(LTextures.loadTexture(fileName), x, y);
}
public Actor(String fileName) {
this(fileName, 0, 0);
}
public void stopAnimation() {
this.isAnimation = false;
}
public void startAnimation() {
this.isAnimation = true;
}
protected void setSize(int w, int h) {
if (boundingRect != null) {
boundingRect.setBounds(_location.x, _location.y, w, h);
} else {
boundingRect = new RectBox(_location.x, _location.y, w, h);
}
}
/**
* 移动当前角色到指定位置并返回MoveTo控制器(flag为true时八方向行走,否则为四方向)
*
* @param x
* @param y
*/
public MoveTo moveTo(int x, int y) {
failIfNotInLayer();
return gameLayer.callMoveTo(this, x, y);
}
/**
* 移动当前角色到指定位置并返回MoveTo控制器(flag为true时八方向行走,否则为四方向)
*
* @param x
* @param y
* @param flag
* @return
*/
public MoveTo moveTo(int x, int y, boolean flag) {
failIfNotInLayer();
return gameLayer.callMoveTo(this, x, y, flag);
}
/**
* 命令当前角色执行淡出操作
*
* @return
*/
public FadeTo fadeOut() {
failIfNotInLayer();
return gameLayer.callFadeOutTo(this, 60);
}
/**
* 命令当前角色执行淡入操作
*
* @return
*/
public FadeTo fadeIn() {
failIfNotInLayer();
return gameLayer.callFadeInTo(this, 60);
}
/**
* 命令当前角色变化为指定颜色
*
* @param end
* @return
*/
public ColorTo colorTo(LColor end) {
failIfNotInLayer();
return gameLayer.callColorTo(this, getColor(), end);
}
/**
* 命令当前角色变化为指定颜色
*
* @param start
* @param end
* @return
*/
public ColorTo colorTo(LColor start, LColor end) {
failIfNotInLayer();
return gameLayer.callColorTo(this, start, end);
}
/**
* 命令当前角色变化为指定颜色
*
* @param start
* @param end
* @param duration
* @param delay
* @return
*/
public ColorTo colorTo(LColor start, LColor end, float duration, float delay) {
failIfNotInLayer();
return gameLayer.callColorTo(this, start, end, duration, delay);
}
/**
* 以指定速度渐进式旋转当前角色到指定角度
*
* @param rotate
* @param speed
* @return
*/
public RotateTo rotateTo(float rotate, float speed) {
failIfNotInLayer();
return gameLayer.callRotateTo(this, rotate, speed);
}
/**
* 渐进式旋转当前角色到指定角度
*
* @param rotate
* @return
*/
public RotateTo rotateTo(float rotate) {
return rotateTo(rotate, 2f);
}
/**
* 以指定加速度指定重力跳跃当前角色
*
* @param jump
* @param g
* @return
*/
public JumpTo jumpTo(int jump, float g) {
failIfNotInLayer();
return gameLayer.callJumpTo(this, -jump, g);
}
/**
* 以指定加速度跳跃当前角色
*
* @param jump
* @return
*/
public JumpTo jumpTo(int jump) {
return jumpTo(jump, 0.3f);
}
/**
* 让指定角色根据指定半径以指定速度循环转动
*
* @param radius
* @param velocity
* @return
*/
public CircleTo circleTo(int radius, int velocity) {
failIfNotInLayer();
return gameLayer.callCircleTo(this, radius, velocity);
}
/**
* 将当前角色作为子弹以指定速度向指定坐标发射
*
* @param endX
* @param endY
* @param speed
* @return
*/
public FireTo fireTo(float endX, float endY, float speed) {
failIfNotInLayer();
return gameLayer.callFireTo(this, endX, endY, speed);
}
/**
* 将当前角色向指定坐标发射
*
* @param endX
* @param endY
* @return
*/
public FireTo fireTo(float endX, float endY) {
return fireTo(endX, endY, 10);
}
/**
* 让当前角色缩放指定大小
*
* @param sx
* @param sy
* @return
*/
public ScaleTo scaleTo(float sx, float sy) {
failIfNotInLayer();
return gameLayer.callScaleTo(this, sx, sy);
}
/**
* 让当前角色缩放指定大小
*
* @param sx
* @param sy
* @return
*/
public ScaleTo scaleTo(float s) {
failIfNotInLayer();
return gameLayer.callScaleTo(this, s, s);
}
/**
* 让指定角色做箭状发射(抛物线)
*
* @param tx
* @param ty
* @return
*/
public ArrowTo arrowTo(float tx, float ty) {
failIfNotInLayer();
return gameLayer.callArrowTo(this, tx, ty);
}
/**
* 振动指定对象
*
* @param shakeX
* @param shakeY
* @return
*/
public ShakeTo shakeTo(float shakeX, float shakeY) {
failIfNotInLayer();
return gameLayer.callShakeTo(this, shakeX, shakeY);
}
/**
* 振动指定对象
*
* @param shakeX
* @param shakeY
* @param duration
* @param delay
* @return
*/
public ShakeTo shakeTo(float shakeX, float shakeY, float duration,
float delay) {
failIfNotInLayer();
return gameLayer.callShakeTo(this, shakeX, shakeY, duration, delay);
}
/**
* 删除所有以当前Actor注册的动作事件
*
*/
public void removeActionEvents() {
failIfNotInLayer();
removeActionEvents(this);
}
/**
* 缩放当前角色
*
* @param scale
*/
public void setScale(final float s) {
this.setScale(s, s);
}
@Override
public void setScale(final float sx, final float sy) {
if (this.scaleX == sx && this.scaleY == sy) {
return;
}
this.scaleX = sx;
this.scaleY = sy;
}
@Override
public float getScaleX() {
return this.scaleX;
}
@Override
public float getScaleY() {
return this.scaleY;
}
/**
* 按下
*
*/
public void downClick(int x, int y) {
}
/**
* 放开
*
*/
public void upClick(int x, int y) {
}
/**
* 键盘按下
*
*/
public void downKey() {
}
/**
* 键盘放开
*
*/
public void upKey() {
}
/**
* 拖拽
*
*/
public void drag(int x, int y) {
}
/**
* 动作处理(内部传参)
*
*/
@Override
public void update(long elapsedTime) {
if (timer.action(elapsedTime)) {
if (isAnimation) {
if (animation != null) {
animation.update(elapsedTime);
setImage(animation.getSpriteImage());
}
}
synchronized (LLayer.class) {
action(elapsedTime);
}
}
}
/**
* 设定动作触发延迟时间
*
* @param delay
*/
public void setDelay(long delay) {
timer.setDelay(delay);
}
/**
* 返回动作触发延迟时间
*
* @return
*/
public long getDelay() {
return timer.getDelay();
}
/**
* 动作处理
*
* @param elapsedTime
*/
public void action(long elapsedTime) {
}
@Override
public int x() {
return _location.x();
}
@Override
public int y() {
return _location.y();
}
@Override
public float getX() {
return _location.x;
}
@Override
public float getY() {
return _location.y;
}
@Override
public float getRotation() {
return this._rotation;
}
/**
* 决定当前对象旋转方向
*
* @param _rotation
*/
@Override
public void setRotation(float r) {
if (r >= 360) {
if (r < 720) {
r -= 360;
} else {
r %= 360;
}
} else if (r < 0) {
if (r >= -360) {
r += 360;
} else {
r = 360 + r % 360;
}
}
if (this._rotation != r) {
this._rotation = r;
this.boundingRect = null;
this.sizeChanged();
}
}
public int width() {
if (image != null) {
return (int) (image.getWidth() * scaleX);
}
return (int) (getRectBox().width * scaleX);
}
public int height() {
if (image != null) {
return (int) (image.getHeight() * scaleY);
}
return (int) (getRectBox().height * scaleY);
}
@Override
public float getWidth() {
return width();
}
@Override
public float getHeight() {
return height();
}
/**
* 根据旋转方向移动坐标
*
* @param distance
*/
public void move(float distance) {
float angle = MathUtils.toRadians(getRotation());
int x = MathUtils.round(getX() + MathUtils.cos(angle) * distance);
int y = MathUtils.round(getY() + MathUtils.sin(angle) * distance);
setLocation(x, y);
}
@Override
public void move(Vector2f v) {
move(v.x, v.y);
}
public void move(int x, int y) {
move(x, y);
}
@Override
public void move(float x, float y) {
setLocationDrag(_location.getX() + x, _location.getY() + y);
}
public void setX(int x) {
this.setLocationDrag(x, y());
}
public void setY(int y) {
this.setLocationDrag(x(), y);
}
@Override
public void setX(float x) {
this.setLocation(x, getY());
}
@Override
public void setY(float y) {
this.setLocation(getX(), y);
}
@Override
public void setX(Integer x) {
setX(x.intValue());
}
@Override
public void setY(Integer y) {
setY(y.intValue());
}
public void setLocation(int x, int y) {
this.setLocationDrag(x, y);
}
@Override
public void setLocation(float x, float y) {
setLocationDrag(x, y);
}
private void setLocationDrag(float x, float y) {
this.failIfNotInLayer();
float oldX = _location.getX();
float oldY = _location.getY();
if (this.gameLayer.isBounded()) {
_location.x = this.limitValue(x, this.gameLayer.getWidth()
- getWidth());
_location.y = this.limitValue(y, this.gameLayer.getHeight()
- getHeight());
} else {
_location.x = x;
_location.y = y;
}
if (_location.x != oldX || _location.y != oldY) {
if (this.boundingRect != null) {
float dx = (_location.getX() - oldX) * this.gameLayer.cellSize;
float dy = (_location.getY() - oldY) * this.gameLayer.cellSize;
this.boundingRect.setX(this.boundingRect.getX() + dx);
this.boundingRect.setY(this.boundingRect.getY() + dy);
for (int i = 0; i < 4; ++i) {
this.xs[i] += dx;
this.ys[i] += dy;
}
}
this.locationChanged(oldX, oldY);
}
}
private float limitValue(float v, float limit) {
if (v < 0) {
v = 0;
}
if (limit < v) {
v = limit;
}
return v;
}
public ActorLayer getLLayer() {
return this.gameLayer;
}
protected void addLayer(ActorLayer gameLayer) {
}
public LTexture getImage() {
return this.image;
}
public void setImage(String filename) {
this.setImage(LTextures.loadTexture(filename));
}
public void setImage(LTexture img) {
if (img != null || this.image != null) {
boolean sizeChanged = true;
if (img != null && this.image != null
&& img.getWidth() == this.image.getWidth()
&& img.getHeight() == this.image.getHeight()) {
sizeChanged = false;
}
if (image != null && image.getParent() == null
&& image.isChildAllClose()) {
if (image != null) {
image.close();
image = null;
}
}
this.image = img;
if (sizeChanged) {
this.boundingRect = null;
this.sizeChanged();
}
}
}
public void setLocationInPixels(float x, float y) {
float xCell = this.gameLayer.toCellFloor(x);
float yCell = this.gameLayer.toCellFloor(y);
if (xCell != _location.x || yCell != _location.y) {
this.setLocationDrag(xCell, yCell);
}
}
void setLayer(ActorLayer gameLayer) {
this.gameLayer = gameLayer;
}
void addLayer(float x, float y, ActorLayer gameLayer) {
if (gameLayer.isBounded()) {
x = this.limitValue(x, gameLayer.getWidth() - getWidth());
y = this.limitValue(y, gameLayer.getHeight() - getHeight());
}
this.boundingRect = null;
this.setLayer(gameLayer);
this.setLocation(x, y);
}
/**
* 获得当前Actor碰撞盒
*
* @return
*/
@Override
public RectBox getRectBox() {
RectBox tmp = getBoundingRect();
if (tmp == null) {
return getRect(_location.x, _location.y, getWidth() * scaleX,
getHeight() * scaleY);
}
return getRect(_location.x, _location.y, tmp.width * scaleX, tmp.height
* scaleY);
}
/**
* 获得当前Actor碰撞盒(内部使用)
*
* @return
*/
RectBox getBoundingRect() {
if (this.boundingRect == null) {
this.calcBounds();
}
return this.boundingRect;
}
/**
* 绘图接口,用以绘制额外的图形到Actor
*
* @param g
*/
public void draw(GLEx g) {
}
public boolean isConsumerDrawing = true;
/**
* 矫正当前图像大小
*
*/
private void calcBounds() {
ActorLayer layer = this.getLLayer();
if (layer != null) {
int width;
int height;
int cellSize = layer.getCellSize();
int minY = 0;
if (this.image == null) {
width = _location.x() * cellSize + cellSize;
height = _location.y() * cellSize + cellSize;
this.boundingRect = new RectBox(width, height, 0, 0);
for (minY = 0; minY < 4; ++minY) {
this.xs[minY] = width;
this.ys[minY] = height;
}
} else {
this.boundingRect = MathUtils.getBounds(_location.x,
_location.y, this.image.getWidth(),
this.image.getHeight(), _rotation);
}
}
}
public RectBox getRandLocation() {
if (gameLayer != null) {
return gameLayer.getRandomLayerLocation(this);
}
return null;
}
public void sendToFront() {
if (gameLayer != null) {
gameLayer.sendToFront(this);
}
}
public void sendToBack() {
if (gameLayer != null) {
gameLayer.sendToBack(this);
}
}
private float[] pos = new float[2];
public float[] toPixels() {
float size = gameLayer.cellSize / 2;
pos[0] = _location.x * gameLayer.cellSize + size;
pos[1] = _location.y * gameLayer.cellSize + size;
return pos;
}
private void sizeChanged() {
if (this.gameLayer != null) {
this.gameLayer.updateObjectSize(this);
}
}
private void locationChanged(float oldX, float oldY) {
if (this.gameLayer != null) {
this.gameLayer.updateObjectLocation(this, oldX, oldY);
}
}
private void failIfNotInLayer() {
if (this.gameLayer == null) {
throw new IllegalStateException(
"The actor has not been inserted into a Layer so it has no _location yet !");
}
}
private static boolean checkOutside(float[] myX, float[] myY,
float[] otherX, float[] otherY) {
for (int v = 0; v < 4; ++v) {
int v1 = v + 1 & 3;
float edgeX = myX[v] - myX[v1];
float edgeY = myY[v] - myY[v1];
float reX = -edgeY;
float reY = edgeX;
if (reX != 0 || edgeX != 0) {
for (int e = 0; e < 4; ++e) {
float scalar = reX * (otherX[e] - myX[v1]) + reY
* (otherY[e] - myY[v1]);
if (scalar < 0) {
continue;
}
}
return true;
}
}
return false;
}
public boolean intersects(Actor other) {
int thisBounds1;
if (this.image == null) {
if (other.image != null) {
thisBounds1 = this.gameLayer.getCellSize();
return other.containsPoint(_location.x() * thisBounds1
+ thisBounds1 / 2, _location.y() * thisBounds1
+ thisBounds1 / 2);
} else {
return _location.x == other._location.x
&& _location.y == other._location.y;
}
} else if (other.image == null) {
thisBounds1 = this.gameLayer.getCellSize();
return this.containsPoint(other._location.x() * thisBounds1
+ thisBounds1 / 2, other._location.y() * thisBounds1
+ thisBounds1 / 2);
} else {
RectBox thisBounds = this.getBoundingRect();
RectBox otherBounds = other.getBoundingRect();
if (this._rotation == 0 && other._rotation == 0) {
return thisBounds.intersects(otherBounds);
} else if (!thisBounds.intersects(otherBounds)) {
return false;
} else {
float[] myX = this.xs;
float[] myY = this.ys;
float[] otherX = other.xs;
float[] otherY = other.ys;
return checkOutside(myX, myY, otherX, otherY) ? false
: !checkOutside(otherX, otherY, myX, myY);
}
}
}
public TArray<Actor> getNeighbours(float distance, boolean diagonal,
String flag) {
this.failIfNotInLayer();
return this.getLLayer().getNeighbours(this, distance, diagonal, flag);
}
public TArray<Actor> getCollisionObjects(float dx, float dy, String flag) {
this.failIfNotInLayer();
return this.gameLayer.getCollisionObjectsAt(_location.x + dx,
_location.y + dy, flag);
}
public Actor getOnlyCollisionObject(float dx, float dy, String flag) {
this.failIfNotInLayer();
return this.gameLayer.getOnlyObjectAt(this, _location.x + dx,
_location.y + dy, flag);
}
public TArray<Actor> getCollisionObjects(float radius, String flag) {
this.failIfNotInLayer();
TArray<Actor> inRange = this.gameLayer.getObjectsInRange(_location.x,
_location.y, radius, flag);
inRange.remove(this);
return inRange;
}
public TArray<Actor> getCollisionObjects() {
return getCollisionObjects(flag);
}
public TArray<Actor> getCollisionObjects(String flag) {
this.failIfNotInLayer();
TArray<Actor> list = this.gameLayer.getIntersectingObjects(this, flag);
list.remove(this);
return list;
}
public Actor getOnlyCollisionObject() {
return getOnlyCollisionObject(flag);
}
public Actor getOnlyCollisionObject(String flag) {
this.failIfNotInLayer();
return this.gameLayer.getOnlyIntersectingObject(this, flag);
}
public Actor getOnlyCollisionObjectAt(float x, float y) {
this.failIfNotInLayer();
return this.gameLayer.getOnlyCollisionObjectsAt(x, y);
}
public Actor getOnlyCollisionObjectAt(float x, float y, Object tag) {
this.failIfNotInLayer();
return this.gameLayer.getOnlyCollisionObjectsAt(x, y, tag);
}
public boolean containsPoint(float px, float py) {
this.failIfNotInLayer();
if (this.image == null) {
return false;
} else {
if (this.boundingRect == null) {
this.calcBounds();
}
if (this._rotation != 0 && this._rotation != 90
&& this._rotation != 270) {
for (int v = 0; v < 4; ++v) {
int v1 = v + 1 & 3;
float edgeX = this.xs[v] - this.xs[v1];
float edgeY = this.ys[v] - this.ys[v1];
float reX = -edgeY;
if (reX != 0 || edgeX != 0) {
float scalar = reX * (px - this.xs[v1]) + edgeX
* (py - this.ys[v1]);
if (scalar >= 0) {
return false;
}
}
}
return true;
} else {
return px >= this.boundingRect.getX()
&& px < this.boundingRect.getRight()
&& py >= this.boundingRect.getY()
&& py < this.boundingRect.getBottom();
}
}
}
@Override
public boolean isVisible() {
return visible;
}
@Override
public void setVisible(boolean visible) {
this.visible = visible;
}
public boolean isDrag() {
return drag;
}
public void setDrag(boolean drag) {
this.drag = drag;
}
public boolean isClick() {
return click;
}
public void setClick(boolean click) {
this.click = click;
}
final void setLastPaintSeqNum(int num) {
this.lastPaintSequenceNumber = num;
}
final public int getSequenceNumber() {
return this.noSequenceNumber;
}
final public int getLastPaintSeqNum() {
return this.lastPaintSequenceNumber;
}
public Animation getAnimation() {
return animation;
}
public void setAnimation(Animation animation) {
if (animation == null) {
throw LSystem.runThrow("Animation is null !");
}
this.animation = animation;
this.isAnimation = true;
this.setImage(animation.getSpriteImage());
}
public boolean isAnimation() {
return isAnimation;
}
public void setAnimation(boolean isAnimation) {
this.isAnimation = isAnimation;
}
@Override
public Field2D getField2D() {
return gameLayer.getField2D();
}
@Override
public boolean isBounded() {
return gameLayer.isBounded();
}
@Override
public boolean isContainer() {
return gameLayer.isContainer();
}
@Override
public boolean inContains(float x, float y, float w, float h) {
return gameLayer.contains(x, y, w, h);
}
@Override
public float getContainerWidth() {
return gameLayer.getWidth();
}
@Override
public float getContainerHeight() {
return gameLayer.getHeight();
}
public boolean isMirror() {
return isFlipX();
}
public void setMirror(boolean m) {
this.setFlipX(m);
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
@Override
public void setWidth(float w) {
this.scaleX = w / getWidth();
}
@Override
public void setHeight(float h) {
this.scaleY = h / getHeight();
}
@Override
public int compareTo(Actor o) {
return o.getLayer() - this.getLayer();
}
@Override
public void setColor(LColor c) {
filterColor = c;
}
@Override
public LColor getColor() {
return new LColor(filterColor);
}
@Override
public Actor setFlipX(boolean x) {
this.flipX = x;
return this;
}
@Override
public Actor setFlipY(boolean y) {
this.flipY = y;
return this;
}
@Override
public Actor setFlipXY(boolean x, boolean y) {
setFlipX(x);
setFlipY(y);
return this;
}
@Override
public boolean isFlipX() {
return flipX;
}
@Override
public boolean isFlipY() {
return flipY;
}
public ActionTween selfAction() {
return PlayerUtils.set(this);
}
public boolean isActionCompleted(){
return PlayerUtils.isActionCompleted(this);
}
@Override
public void close() {
if (image != null) {
image.close();
}
if (animation != null) {
animation.close();
}
setState(State.DISPOSED);
removeActionEvents(this);
}
}