/*
* Copyright 2014 Google Inc. All rights reserved.
*
* 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.
*/
package com.google.fpl.gamecontroller.particles;
import com.google.fpl.gamecontroller.GameState;
import com.google.fpl.gamecontroller.ShapeBuffer;
import com.google.fpl.gamecontroller.Utils;
/**
* Base class used to handle particle effects.
*/
public class BaseParticle {
public static final int PARTICLE_TYPE_NORMAL = 0;
// Rocket particles have different collision behavior, produce a trail of exhaust, and
// have an acceleration.
public static final int PARTICLE_TYPE_ROCKET = 1;
// Particles fade in and out over a 1 second period.
private static final float FADE_FRAME_COUNT = GameState.secondsToFrameDelta(1.0f);
private static final float FADE_DELTA_PER_FRAME = 1.0f / FADE_FRAME_COUNT;
// The max velocity of rocket particles.
private static final float ROCKET_MAX_SPEED_SQUARED = 6.0f * 6.0f;
// Rocket acceleration per frame (expressed as a percentage increase increase over the
// rocket's current speed).
private static final float ROCKET_ACCELERATION = 0.05f;
// Particles with an active frame count of 0 are not drawn or updated.
private float mActiveFrameCountRemaining = 0.0f;
// Either PARTICLE_TYPE_NORMAL or PARTICLE_TYPE_ROCKET.
private int mParticleType;
// Screen-space position of the center of the particle.
private float mPositionX, mPositionY;
// The distance this particle moves each frame.
private float mVelocityX, mVelocityY;
// Scales the coordinates Utils.SQUARE_SHAPE to create larger or smaller particles.
private float mSize;
// The color of the particle.
private final Utils.Color mColor = new Utils.Color();
// A value between 0 and 1 used to scale the alpha value of the color. As particles fade
// in and out, the alpha value in mColor changes. When a particle is not in the process of
// fading, its transparency will be set to mMaxAlpha.
private float mMaxAlpha;
// The ratio of the particle's width to height. Particles that have an aspect ratio other
// than 1.0 will automatically rotate to point in the direction they are traveling.
private float mAspectRatio;
// The id of the Spaceship that owns this particle. Used for bullet particles to award
// points.
private int mOwnerId;
// If true, the particle will become inactive as soon as its center passes outside the
// bounds of the world.
private boolean mDieOffscreen;
private final Utils.Color mCurrentColor = new Utils.Color();
/**
* Returns a particle to its default state.
*
* This function is called to initialize newly spawned particles.
*
* @param lifetimeFrameCount the total number of frames this particle will be active.
*/
public void reset(float lifetimeFrameCount) {
this.mActiveFrameCountRemaining = lifetimeFrameCount;
this.mParticleType = PARTICLE_TYPE_NORMAL;
this.mPositionX = 0.0f;
this.mPositionY = 0.0f;
this.mVelocityX = 0.0f;
this.mVelocityY = 0.0f;
this.mSize = 1.0f;
this.mColor.set(1.0f, 1.0f, 1.0f, 1.0f);
this.mMaxAlpha = 1.0f;
this.mAspectRatio = 1.0f;
this.mOwnerId = GameState.INVALID_PLAYER_ID;
this.mDieOffscreen = true;
// Set newly created particles to be transparent so that particles will not be visible
// until they have had update() called on them. This avoids ordering issues that can
// occur when particles are created during the update phase of the frame.
this.mCurrentColor.setAlpha(0.0f);
}
public void setParticleType(int particleType) {
this.mParticleType = particleType;
}
public void setPosition(float x, float y) {
this.mPositionX = x;
this.mPositionY = y;
}
public float getPositionX() {
return mPositionX;
}
public float getPositionY() {
return mPositionY;
}
public void setSpeed(float speedX, float speedY) {
this.mVelocityX = speedX;
this.mVelocityY = speedY;
}
public void setSize(float size) {
this.mSize = size;
}
public float getSize() {
return mSize;
}
public void setColor(Utils.Color color) {
this.mColor.set(color);
}
public Utils.Color getColor() {
return mColor;
}
public void setMaxAlpha(float maxAlpha) {
this.mMaxAlpha = maxAlpha;
}
public void setAspectRatio(float aspectRatio) {
this.mAspectRatio = aspectRatio;
}
public void setOwnerId(int ownerId) {
this.mOwnerId = ownerId;
}
public int getOwnerId() {
return mOwnerId;
}
public void setDieOffscreen(boolean dieOffscreen) {
this.mDieOffscreen = dieOffscreen;
}
public void update(float frameDelta) {
if (!isActive()) {
return;
}
incrementPosition(frameDelta);
mActiveFrameCountRemaining -= frameDelta;
// Update the particle's alpha every frame.
float newAlpha = mColor.alpha();
if (mActiveFrameCountRemaining < FADE_FRAME_COUNT) {
// Particles that are about to die will fade out.
newAlpha -= FADE_DELTA_PER_FRAME * frameDelta;
} else {
// Newly created particles fade in.
newAlpha += FADE_DELTA_PER_FRAME * frameDelta;
}
mColor.setAlpha(Utils.clamp(newAlpha, 0.0f, 1.0f));
if (mDieOffscreen) {
if (!GameState.inWorld(mPositionX, mPositionY)) {
// The particle is outside the screen, so kill it.
mActiveFrameCountRemaining = 0.0f;
}
}
// Special update for rockets.
if (mParticleType == PARTICLE_TYPE_ROCKET) {
handleRocketUpdate(frameDelta);
}
mCurrentColor.set(mColor);
mCurrentColor.setAlpha(mCurrentColor.alpha() * mMaxAlpha);
}
protected void handleRocketUpdate(float frameDelta) {
float clampedX = Utils.clamp(mPositionX, GameState.MAP_LEFT_COORDINATE,
GameState.MAP_RIGHT_COORDINATE);
float clampedY = Utils.clamp(mPositionY, GameState.MAP_BOTTOM_COORDINATE,
GameState.MAP_TOP_COORDINATE);
if (clampedX != mPositionX || clampedY != mPositionY) {
// The rocket hit the edge of the map, so make it explode.
mPositionX = clampedX;
mPositionY = clampedY;
handleCollision();
}
// The rocket particle will accelerate up to a maximum speed.
float currentSpeedSquared = Utils.vector2DLengthSquared(mVelocityX, mVelocityY);
if (currentSpeedSquared <= ROCKET_MAX_SPEED_SQUARED) {
mVelocityX *= ROCKET_ACCELERATION * frameDelta + 1.0f;
mVelocityY *= ROCKET_ACCELERATION * frameDelta + 1.0f;
}
// Create a particle trail (exhaust) behind the rocket.
GameState.getInstance().getExplosions().spawnExhaustTrail(
mPositionX, mPositionY,
mVelocityX, mVelocityY,
mColor, 1);
}
/**
* Advance the particle's position by the given number of frames.
*/
public void incrementPosition(float frameDelta) {
mPositionX += mVelocityX * frameDelta;
mPositionY += mVelocityY * frameDelta;
}
public void draw(ShapeBuffer sb) {
float headingX = 0.0f, headingY = 0.0f;
if (mAspectRatio != 1.0f) {
// Non-square particles point in the direction they are moving.
headingX = mVelocityX;
headingY = mVelocityY;
}
sb.add2DShape(
mPositionX, mPositionY,
mCurrentColor,
Utils.SQUARE_SHAPE,
mSize * mAspectRatio, mSize,
headingX, headingY);
}
public void handleCollision() {
if (mParticleType == PARTICLE_TYPE_ROCKET) {
// Add the shrapnel particles to the "shots" layer so that they will be checked
// for collisions with other players.
GameState.getInstance().getShots().spawnShrapnelExplosion(mPositionX, mPositionY,
mColor, 0.5f, 1.5f, mOwnerId, 100);
} else {
// Create a little bit of "smoke" when the particle hits something.
GameState.getInstance().getExplosions().spawnRingBurst(mPositionX, mPositionY, mColor,
0.15f, 0.75f, 5);
}
mActiveFrameCountRemaining = 0.0f;
}
public boolean isActive() {
return mActiveFrameCountRemaining > 0.0f;
}
}