)params.get("positions");
31 | for (List pos : positions) {
32 | float[] parray = new float[] {asFloat(pos.get(0)), asFloat(pos.get(1)), asFloat(pos.get(2)), asFloat(pos.get(3))};
33 | float restitution = 0f;
34 | Body wallBody = Box2DFactory.createThinWall(world, parray[0], parray[1], parray[2], parray[3], restitution);
35 | allBodies.add(wallBody);
36 | bodyPositions.put(wallBody, parray);
37 | }
38 | }
39 |
40 | @Override
41 | public Collection getBodies () {
42 | return allBodies;
43 | }
44 |
45 | /** Returns true if all targets have been hit (and their corresponding bodies made inactive) */
46 | public boolean allTargetsHit () {
47 | for (Body body : allBodies) {
48 | if (body.isActive()) return false;
49 | }
50 | return true;
51 | }
52 |
53 | @Override
54 | public void handleCollision (Body ball, Body bodyHit, final Field field) {
55 | bodyHit.setActive(false);
56 | // if all hit, notify delegate and check for reset parameter
57 | if (allTargetsHit()) {
58 | field.getDelegate().allDropTargetsInGroupHit(field, this);
59 |
60 | float restoreTime = asFloat(this.parameters.get("reset"));
61 | if (restoreTime > 0) {
62 | field.scheduleAction((long)(restoreTime * 1000), new Runnable() {
63 | public void run () {
64 | makeAllTargetsVisible(field);
65 | }
66 | });
67 | }
68 | }
69 | }
70 |
71 | /** Makes all targets visible by calling Body.setActive(true) on each target body */
72 | public void makeAllTargetsVisible (Field field) {
73 | for (Body body : allBodies) {
74 | body.setActive(true);
75 | }
76 | }
77 |
78 | @Override
79 | public void draw (IFieldRenderer renderer) {
80 | // draw line for each target
81 | int r = redColorComponent(0);
82 | int g = greenColorComponent(255);
83 | int b = blueColorComponent(0);
84 |
85 | int len = allBodies.size();
86 | for (int i = 0; i < len; i++) {
87 | Body body = allBodies.get(i);
88 | if (body.isActive()) {
89 | float[] parray = bodyPositions.get(body);
90 | renderer.drawLine(parray[0], parray[1], parray[2], parray[3], r, g, b);
91 | }
92 | }
93 |
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/util/JSONStringer.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy.util;
3 |
4 | /*
5 | Copyright (c) 2006 JSON.org
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | The Software shall be used for Good, not Evil.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | SOFTWARE.
26 | */
27 |
28 | import java.io.StringWriter;
29 |
30 | /** JSONStringer provides a quick and convenient way of producing JSON text. The texts produced strictly conform to JSON syntax
31 | * rules. No whitespace is added, so the results are ready for transmission or storage. Each instance of JSONStringer can produce
32 | * one JSON text.
33 | *
34 | * A JSONStringer instance provides a value method for appending values to the text, and a key method
35 | * for adding keys before values in objects. There are array and endArray methods that make and bound
36 | * array values, and object and endObject methods which make and bound object values. All of these
37 | * methods return the JSONWriter instance, permitting cascade style. For example,
38 | *
39 | *
40 | * myString = new JSONStringer().object().key("JSON").value("Hello, World!").endObject().toString();
41 | *
42 | *
43 | * which produces the string
44 | *
45 | *
46 | * {"JSON":"Hello, World!"}
47 | *
48 | *
49 | * The first method called must be array or object. There are no methods for adding commas or colons.
50 | * JSONStringer adds them for you. Objects and arrays can be nested up to 20 levels deep.
51 | *
52 | * This can sometimes be easier than using a JSONObject to build a string.
53 | * @author JSON.org
54 | * @version 2008-09-18 */
55 | public class JSONStringer extends JSONWriter {
56 | /** Make a fresh JSONStringer. It can be used to build one JSON text. */
57 | public JSONStringer () {
58 | super(new StringWriter());
59 | }
60 |
61 | /** Return the JSON text. This method is used to obtain the product of the JSONStringer instance. It will return
62 | * null if there was a problem in the construction of the JSON text (such as the calls to array were
63 | * not properly balanced with calls to endArray).
64 | * @return The JSON text. */
65 | public String toString () {
66 | return this.mode == 'd' ? this.writer.toString() : null;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/Bouncy.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy;
3 |
4 | import com.badlogic.gdx.ApplicationListener;
5 | import com.badlogic.gdx.Gdx;
6 | import com.badlogic.gdx.InputAdapter;
7 | import com.badlogic.gdx.graphics.GL20;
8 | import com.badlogic.gdx.graphics.OrthographicCamera;
9 | import com.badlogic.gdx.math.WindowedMean;
10 | import com.badlogic.gdx.utils.TimeUtils;
11 |
12 | import com.dozingcatsoftware.bouncy.elements.FieldElement;
13 |
14 | public class Bouncy extends InputAdapter implements ApplicationListener {
15 | OrthographicCamera cam;
16 | GLFieldRenderer renderer;
17 | Field field;
18 | int level = 1;
19 | WindowedMean physicsMean = new WindowedMean(10);
20 | WindowedMean renderMean = new WindowedMean(10);
21 | long startTime = TimeUtils.nanoTime();
22 |
23 | @Override
24 | public void create () {
25 | cam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
26 | renderer = new GLFieldRenderer();
27 | field = new Field();
28 | field.resetForLevel(level);
29 | Gdx.input.setInputProcessor(this);
30 | }
31 |
32 | @Override
33 | public void resume () {
34 |
35 | }
36 |
37 | @Override
38 | public void render () {
39 | GL20 gl = Gdx.gl;
40 |
41 | long startPhysics = TimeUtils.nanoTime();
42 | field.tick((long)(Gdx.graphics.getDeltaTime() * 3000), 4);
43 | physicsMean.addValue((TimeUtils.nanoTime() - startPhysics) / 1000000000.0f);
44 |
45 | gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
46 | cam.viewportWidth = field.getWidth();
47 | cam.viewportHeight = field.getHeight();
48 | cam.position.set(field.getWidth() / 2, field.getHeight() / 2, 0);
49 | cam.update();
50 | renderer.setProjectionMatrix(cam.combined);
51 |
52 | long startRender = TimeUtils.nanoTime();
53 | renderer.begin();
54 | int len = field.getFieldElements().size();
55 | for (int i = 0; i < len; i++) {
56 | FieldElement element = field.getFieldElements().get(i);
57 | element.draw(renderer);
58 | }
59 | renderer.end();
60 |
61 | renderer.begin();
62 | field.drawBalls(renderer);
63 | renderer.end();
64 | renderMean.addValue((TimeUtils.nanoTime() - startRender) / 1000000000.0f);
65 |
66 | if (TimeUtils.nanoTime() - startTime > 1000000000) {
67 | Gdx.app.log("Bouncy", "fps: " + Gdx.graphics.getFramesPerSecond() + ", physics: " + physicsMean.getMean() * 1000
68 | + ", rendering: " + renderMean.getMean() * 1000);
69 | startTime = TimeUtils.nanoTime();
70 | }
71 | }
72 |
73 | @Override
74 | public void resize (int width, int height) {
75 |
76 | }
77 |
78 | @Override
79 | public void pause () {
80 |
81 | }
82 |
83 | @Override
84 | public void dispose () {
85 |
86 | }
87 |
88 | @Override
89 | public boolean touchDown (int x, int y, int pointer, int button) {
90 | field.removeDeadBalls();
91 | if (field.getBalls().size() != 0) field.setAllFlippersEngaged(true);
92 | return false;
93 | }
94 |
95 | @Override
96 | public boolean touchUp (int x, int y, int pointer, int button) {
97 | field.removeDeadBalls();
98 | if (field.getBalls().size() == 0) field.launchBall();
99 | field.setAllFlippersEngaged(false);
100 | return false;
101 | }
102 |
103 | @Override
104 | public boolean touchDragged (int x, int y, int pointer) {
105 | if (field.getBalls().size() != 0) field.setAllFlippersEngaged(true);
106 | return false;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/elements/WallArcElement.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy.elements;
3 |
4 | import java.util.ArrayList;
5 | import java.util.Collection;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import com.badlogic.gdx.physics.box2d.Body;
10 | import com.badlogic.gdx.physics.box2d.World;
11 | import com.dozingcatsoftware.bouncy.IFieldRenderer;
12 |
13 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*;
14 |
15 | /** This FieldElement subclass approximates a circular wall with a series of straight wall segments whose endpoints lie on a circle
16 | * or ellipse. These elements are defined in the layout JSON as follows: { "class": "WallArcElement", "center": [5.5, 10], //
17 | * center of circle or ellipse "xradius": 2.5, // radius in the horizontal direction "yradius": 2, // radius in the y direction
18 | * "minangle": 45, // starting angle in degrees, 0 is to the right of the center, 90 is up. "maxangle": 135, // ending angle in
19 | * degrees "segments": 10, // number of straight wall segments to use to approximate the arc. "color": [255,0,0] // optional RGB
20 | * values for the arc's color }
21 | *
22 | * For circular walls, the "radius" attribute can be used instead of xradius and yradius.
23 | *
24 | * @author brian */
25 |
26 | public class WallArcElement extends FieldElement {
27 |
28 | public List wallBodies = new ArrayList();
29 | List lineSegments = new ArrayList();
30 |
31 | public void finishCreate (Map params, World world) {
32 | List centerPos = (List)params.get("center");
33 | float cx = asFloat(centerPos.get(0));
34 | float cy = asFloat(centerPos.get(1));
35 |
36 | // can specify "radius" for circle, or "xradius" and "yradius" for ellipse
37 | float xradius, yradius;
38 | if (params.containsKey("radius")) {
39 | xradius = yradius = asFloat(params.get("radius"));
40 | } else {
41 | xradius = asFloat(params.get("xradius"));
42 | yradius = asFloat(params.get("yradius"));
43 | }
44 |
45 | Number segments = (Number)params.get("segments");
46 | int numsegments = (segments != null) ? segments.intValue() : 5;
47 | float minangle = toRadians(asFloat(params.get("minangle")));
48 | float maxangle = toRadians(asFloat(params.get("maxangle")));
49 | float diff = maxangle - minangle;
50 | // create numsegments line segments to approximate circular arc
51 | for (int i = 0; i < numsegments; i++) {
52 | float angle1 = minangle + i * diff / numsegments;
53 | float angle2 = minangle + (i + 1) * diff / numsegments;
54 | float x1 = cx + xradius * (float)Math.cos(angle1);
55 | float y1 = cy + yradius * (float)Math.sin(angle1);
56 | float x2 = cx + xradius * (float)Math.cos(angle2);
57 | float y2 = cy + yradius * (float)Math.sin(angle2);
58 |
59 | Body wall = Box2DFactory.createThinWall(world, x1, y1, x2, y2, 0f);
60 | this.wallBodies.add(wall);
61 | lineSegments.add(new float[] {x1, y1, x2, y2});
62 | }
63 | }
64 |
65 | @Override
66 | public Collection getBodies () {
67 | return wallBodies;
68 | }
69 |
70 | @Override
71 | public void draw (IFieldRenderer renderer) {
72 | int len = lineSegments.size();
73 | for (int i = 0; i < len; i++) {
74 | float[] segment = lineSegments.get(i);
75 | renderer.drawLine(segment[0], segment[1], segment[2], segment[3], redColorComponent(DEFAULT_WALL_RED),
76 | greenColorComponent(DEFAULT_WALL_GREEN), blueColorComponent(DEFAULT_WALL_BLUE));
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/elements/WallElement.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy.elements;
3 |
4 | import java.util.Collection;
5 | import java.util.Collections;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import com.badlogic.gdx.math.Vector2;
10 | import com.badlogic.gdx.physics.box2d.Body;
11 | import com.badlogic.gdx.physics.box2d.World;
12 | import com.dozingcatsoftware.bouncy.Field;
13 | import com.dozingcatsoftware.bouncy.IFieldRenderer;
14 |
15 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*;
16 |
17 | /** FieldElement subclass that represents a straight wall. Its position is specified by the "position" parameter with 4 values,
18 | * which are [start x, start y, end x, end y]. There are several optional parameters to customize the wall's behavior: "kick":
19 | * impulse to apply when a ball hits the wall, used for kickers and ball savers. "kill": if true, the ball is lost when it hits
20 | * the wall. Used for invisible wall below the flippers. "retractWhenHit": if true, the wall is removed when hit by a ball. Used
21 | * for ball savers. "disabled": if true, the wall starts out retracted, and will only be shown when setRetracted(field, true) is
22 | * called.
23 | *
24 | * Walls can be removed from the field by calling setRetracted(field, true), and restored with setRetracted(field, false).
25 | *
26 | * @author brian */
27 |
28 | public class WallElement extends FieldElement {
29 |
30 | Body wallBody;
31 | Collection bodySet;
32 | float x1, y1, x2, y2;
33 | float kick;
34 |
35 | boolean killBall;
36 | boolean retractWhenHit;
37 |
38 | @Override
39 | public void finishCreate (Map params, World world) {
40 | List pos = (List)params.get("position");
41 | this.x1 = asFloat(pos.get(0));
42 | this.y1 = asFloat(pos.get(1));
43 | this.x2 = asFloat(pos.get(2));
44 | this.y2 = asFloat(pos.get(3));
45 | float restitution = asFloat(params.get("restitution"));
46 |
47 | wallBody = Box2DFactory.createThinWall(world, x1, y1, x2, y2, restitution);
48 | bodySet = Collections.singleton(wallBody);
49 |
50 | this.kick = asFloat(params.get("kick"));
51 | this.killBall = (Boolean.TRUE.equals(params.get("kill")));
52 | this.retractWhenHit = (Boolean.TRUE.equals(params.get("retractWhenHit")));
53 |
54 | boolean disabled = Boolean.TRUE.equals(params.get("disabled"));
55 | if (disabled) {
56 | setRetracted(true);
57 | }
58 | }
59 |
60 | public boolean isRetracted () {
61 | return !wallBody.isActive();
62 | }
63 |
64 | public void setRetracted (boolean retracted) {
65 | if (retracted != this.isRetracted()) {
66 | wallBody.setActive(!retracted);
67 | }
68 | }
69 |
70 | @Override
71 | public Collection getBodies () {
72 | return bodySet;
73 | }
74 |
75 | @Override
76 | public boolean shouldCallTick () {
77 | // tick() only needs to be called if this wall provides a kick which makes it flash
78 | return (this.kick > 0.01f);
79 | }
80 |
81 | Vector2 impulseForBall (Body ball) {
82 | if (this.kick <= 0.01f) return null;
83 | // rotate wall direction 90 degrees for normal, choose direction toward ball
84 | float ix = this.y2 - this.y1;
85 | float iy = this.x1 - this.x2;
86 | float mag = (float)Math.sqrt(ix * ix + iy * iy);
87 | float scale = this.kick / mag;
88 | ix *= scale;
89 | iy *= scale;
90 |
91 | // dot product of (ball center - wall center) and impulse direction should be positive, if not flip impulse
92 | Vector2 balldiff = ball.getWorldCenter().cpy().sub(this.x1, this.y1);
93 | float dotprod = balldiff.x * ix + balldiff.y * iy;
94 | if (dotprod < 0) {
95 | ix = -ix;
96 | iy = -iy;
97 | }
98 |
99 | return new Vector2(ix, iy);
100 | }
101 |
102 | @Override
103 | public void handleCollision (Body ball, Body bodyHit, Field field) {
104 | if (retractWhenHit) {
105 | this.setRetracted(true);
106 | }
107 |
108 | if (killBall) {
109 | field.removeBall(ball);
110 | } else {
111 | Vector2 impulse = this.impulseForBall(ball);
112 | if (impulse != null) {
113 | ball.applyLinearImpulse(impulse, ball.getWorldCenter(), true);
114 | flashForFrames(3);
115 | }
116 | }
117 | }
118 |
119 | @Override
120 | public void draw (IFieldRenderer renderer) {
121 | if (isRetracted()) return;
122 | renderer.drawLine(x1, y1, x2, y2, redColorComponent(DEFAULT_WALL_RED), greenColorComponent(DEFAULT_WALL_GREEN),
123 | blueColorComponent(DEFAULT_WALL_BLUE));
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | buildToolsVersion project.androidToolsVersion
3 | compileSdkVersion project.androidSDKVersion.toInteger()
4 | sourceSets {
5 | main {
6 | manifest.srcFile 'AndroidManifest.xml'
7 | java.srcDirs = ['src']
8 | aidl.srcDirs = ['src']
9 | renderscript.srcDirs = ['src']
10 | res.srcDirs = ['res']
11 | assets.srcDirs = ['assets']
12 | jniLibs.srcDirs = ['libs']
13 | }
14 | }
15 | packagingOptions {
16 | exclude 'META-INF/robovm/ios/robovm.xml'
17 | }
18 | defaultConfig {
19 | applicationId "com.dozingcatsoftware.bouncy.android"
20 | minSdkVersion 14
21 | targetSdkVersion 30
22 | versionCode 1
23 | versionName "1.0"
24 | }
25 | buildTypes {
26 | release {
27 | minifyEnabled true
28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | }
32 |
33 | // called every time gradle gets executed, takes the native dependencies of
34 | // the natives configuration, and extracts them to the proper libs/ folders
35 | // so they get packed with the APK.
36 | task copyAndroidNatives {
37 | doFirst {
38 | file("libs/armeabi-v7a/").mkdirs()
39 | file("libs/arm64-v8a/").mkdirs()
40 | file("libs/x86_64/").mkdirs()
41 | file("libs/x86/").mkdirs()
42 |
43 | configurations.natives.files.each { jar ->
44 | def outputDir = null
45 | if (jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a")
46 | if (jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a")
47 | if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64")
48 | if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86")
49 | if(outputDir != null) {
50 | copy {
51 | from zipTree(jar)
52 | into outputDir
53 | include "*.so"
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | tasks.whenTaskAdded { packageTask ->
61 | if (packageTask.name.contains("package")) {
62 | packageTask.dependsOn 'copyAndroidNatives'
63 | }
64 | }
65 |
66 | task run(type: Exec) {
67 | def path
68 | def localProperties = project.file("../local.properties")
69 | if (localProperties.exists()) {
70 | Properties properties = new Properties()
71 | localProperties.withInputStream { instr ->
72 | properties.load(instr)
73 | }
74 | def sdkDir = properties.getProperty('sdk.dir')
75 | if (sdkDir) {
76 | path = sdkDir
77 | } else {
78 | path = "$System.env.ANDROID_HOME"
79 | }
80 | } else {
81 | path = "$System.env.ANDROID_HOME"
82 | }
83 |
84 | def adb = path + "/platform-tools/adb"
85 | commandLine "$adb", 'shell', 'am', 'start', '-n', 'com.dozingcatsoftware.bouncy.android/com.dozingcatsoftware.bouncy.android.AndroidLauncher'
86 | }
87 |
88 | // sets up the Android Eclipse project, using the old Ant based build.
89 | eclipse {
90 | // need to specify Java source sets explicitely, SpringSource Gradle Eclipse plugin
91 | // ignores any nodes added in classpath.file.withXml
92 | sourceSets {
93 | main {
94 | java.srcDirs "src", 'gen'
95 | }
96 | }
97 |
98 | jdt {
99 | sourceCompatibility = 1.6
100 | targetCompatibility = 1.6
101 | }
102 |
103 | classpath {
104 | plusConfigurations += [ project.configurations.compile ]
105 | containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES'
106 | }
107 |
108 | project {
109 | name = appName + "-android"
110 | natures 'com.android.ide.eclipse.adt.AndroidNature'
111 | buildCommands.clear();
112 | buildCommand "com.android.ide.eclipse.adt.ResourceManagerBuilder"
113 | buildCommand "com.android.ide.eclipse.adt.PreCompilerBuilder"
114 | buildCommand "org.eclipse.jdt.core.javabuilder"
115 | buildCommand "com.android.ide.eclipse.adt.ApkBuilder"
116 | }
117 | }
118 |
119 | // sets up the Android Idea project, using the old Ant based build.
120 | idea {
121 | module {
122 | sourceDirs += file("src");
123 | scopes = [ COMPILE: [plus:[project.configurations.compile]]]
124 |
125 | iml {
126 | withXml {
127 | def node = it.asNode()
128 | def builder = NodeBuilder.newInstance();
129 | builder.current = node;
130 | builder.component(name: "FacetManager") {
131 | facet(type: "android", name: "Android") {
132 | configuration {
133 | option(name: "UPDATE_PROPERTY_FILES", value:"true")
134 | }
135 | }
136 | }
137 | }
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/FieldLayout.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy;
3 |
4 | import java.io.BufferedReader;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.Collections;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Random;
13 |
14 | import com.badlogic.gdx.Gdx;
15 | import com.badlogic.gdx.physics.box2d.World;
16 | import com.dozingcatsoftware.bouncy.elements.FieldElement;
17 | import com.dozingcatsoftware.bouncy.elements.FlipperElement;
18 | import com.dozingcatsoftware.bouncy.util.JSONUtils;
19 |
20 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*;
21 |
22 | public class FieldLayout {
23 |
24 | static List _layoutArray;
25 | Random RAND = new Random();
26 |
27 | static void readLayoutArray () {
28 | try {
29 | InputStream fin = Gdx.files.internal("data/field_layout.json").read();
30 | BufferedReader br = new BufferedReader(new InputStreamReader(fin));
31 |
32 | StringBuilder buffer = new StringBuilder();
33 | String line;
34 | while ((line = br.readLine()) != null) {
35 | buffer.append(line);
36 | }
37 | fin.close();
38 | _layoutArray = JSONUtils.listFromJSONString(buffer.toString());
39 | } catch (Exception ex) {
40 | ex.printStackTrace();
41 | }
42 | }
43 |
44 | public static FieldLayout layoutForLevel (int level, World world) {
45 | try {
46 | if (_layoutArray == null) readLayoutArray();
47 | Map layoutMap = (Map)_layoutArray.get(level - 1);
48 | return new FieldLayout(layoutMap, world);
49 | } catch (Exception ex) {
50 | ex.printStackTrace();
51 | return null;
52 | }
53 | }
54 |
55 | public static int numberOfLevels () {
56 | return _layoutArray.size();
57 | }
58 |
59 | List fieldElements = new ArrayList();
60 | List flippers;
61 | float width;
62 | float height;
63 | List ballColor;
64 | float targetTimeRatio;
65 | Map allParameters;
66 |
67 | static List DEFAULT_BALL_COLOR = Arrays.asList(255, 0, 0);
68 |
69 | static List listForKey (Map map, String key) {
70 | if (map.containsKey(key)) return (List)map.get(key);
71 | return Collections.EMPTY_LIST;
72 | }
73 |
74 | List addFieldElements (Map layoutMap, String key, Class defaultClass, World world) {
75 | List elements = new ArrayList();
76 | for (Object obj : listForKey(layoutMap, key)) {
77 | // allow strings in JSON for comments
78 | if (!(obj instanceof Map)) continue;
79 | Map params = (Map)obj;
80 | elements.add(FieldElement.createFromParameters(params, world, defaultClass));
81 | }
82 | fieldElements.addAll(elements);
83 | return elements;
84 | }
85 |
86 | public FieldLayout (Map layoutMap, World world) {
87 | this.width = asFloat(layoutMap.get("width"), 20.0f);
88 | this.height = asFloat(layoutMap.get("height"), 30.0f);
89 | this.targetTimeRatio = asFloat(layoutMap.get("targetTimeRatio"));
90 | this.ballColor = (layoutMap.containsKey("ballcolor")) ? (List)layoutMap.get("ballcolor") : DEFAULT_BALL_COLOR;
91 | this.allParameters = layoutMap;
92 |
93 | flippers = addFieldElements(layoutMap, "flippers", FlipperElement.class, world);
94 |
95 | addFieldElements(layoutMap, "elements", null, world);
96 | }
97 |
98 | public List getFieldElements () {
99 | return fieldElements;
100 | }
101 |
102 | public List getFlipperElements () {
103 | return flippers;
104 | }
105 |
106 | public float getBallRadius () {
107 | return asFloat(allParameters.get("ballradius"), 0.5f);
108 | }
109 |
110 | public List getBallColor () {
111 | return ballColor;
112 | }
113 |
114 | public int getNumberOfBalls () {
115 | return (allParameters.containsKey("numballs")) ? ((Number)allParameters.get("numballs")).intValue() : 3;
116 | }
117 |
118 | public List getLaunchPosition () {
119 | Map launchMap = (Map)allParameters.get("launch");
120 | return (List)launchMap.get("position");
121 | }
122 |
123 | // can apply random velocity increment if specified by "random_velocity" key
124 | public List getLaunchVelocity () {
125 | Map launchMap = (Map)allParameters.get("launch");
126 | List velocity = (List)launchMap.get("velocity");
127 | float vx = velocity.get(0).floatValue();
128 | float vy = velocity.get(1).floatValue();
129 |
130 | if (launchMap.containsKey("random_velocity")) {
131 | List delta = (List)launchMap.get("random_velocity");
132 | if (delta.get(0).floatValue() > 0) vx += delta.get(0).floatValue() * RAND.nextFloat();
133 | if (delta.get(1).floatValue() > 0) vy += delta.get(1).floatValue() * RAND.nextFloat();
134 | }
135 | return Arrays.asList(vx, vy);
136 | }
137 |
138 | public float getWidth () {
139 | return width;
140 | }
141 |
142 | public float getHeight () {
143 | return height;
144 | }
145 |
146 | /** Returns the desired ratio between real world time and simulation time. The application should adjust the frame rate and/or
147 | * time interval passed to Field.tick() to keep the ratio as close to this value as possible. */
148 | public float getTargetTimeRatio () {
149 | return targetTimeRatio;
150 | }
151 |
152 | /** Returns the magnitude of the gravity vector. */
153 | public float getGravity () {
154 | return asFloat(allParameters.get("gravity"), 4.0f);
155 | }
156 |
157 | public String getDelegateClassName () {
158 | return (String)allParameters.get("delegate");
159 | }
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/elements/FlipperElement.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy.elements;
3 |
4 | import java.util.Collection;
5 | import java.util.Collections;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import com.badlogic.gdx.math.Vector2;
10 | import com.badlogic.gdx.physics.box2d.Body;
11 | import com.badlogic.gdx.physics.box2d.BodyDef;
12 | import com.badlogic.gdx.physics.box2d.World;
13 | import com.badlogic.gdx.physics.box2d.joints.RevoluteJoint;
14 | import com.badlogic.gdx.physics.box2d.joints.RevoluteJointDef;
15 | import com.dozingcatsoftware.bouncy.Field;
16 | import com.dozingcatsoftware.bouncy.IFieldRenderer;
17 |
18 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*;
19 |
20 | /** FieldElement subclass for a flipper that is controlled by the player. A flipper consists of a Box2D RevoluteJoint where a thin
21 | * wall rotates around an invisible anchor. Flippers are defined in the layout JSON as follows: { "class": "FlipperElement",
22 | * "position": [5.5, 10], // x,y of fixed end of flipper which it rotates around "length": 2.5, // length of the flipper. Negative
23 | * if the flipper rotates around its right end. "minangle": -20, // minimum angle from the horizontal. Negative angles are below
24 | * horizontal. "maxangle": 20, // maximum angle from the horizontal. "upspeed": 7, // rate at which the flipper rotates up when
25 | * activated (in radians/sec?) "downspeed": 3 // rate at which the flipper rotates down when not activated (in radians/sec?) }
26 | *
27 | * @author brian */
28 |
29 | public class FlipperElement extends FieldElement {
30 |
31 | Body flipperBody;
32 | Collection flipperBodySet;
33 | public Body anchorBody;
34 | public RevoluteJoint joint;
35 | RevoluteJointDef jointDef;
36 |
37 | float flipperLength; // negative if flipper rotates around its right end
38 | float upspeed, downspeed;
39 | float minangle, maxangle;
40 | float cx, cy;
41 |
42 | @Override
43 | public void finishCreate (Map params, World world) {
44 | List pos = (List)params.get("position");
45 |
46 | this.cx = asFloat(pos.get(0));
47 | this.cy = asFloat(pos.get(1));
48 | this.flipperLength = asFloat(params.get("length"));
49 | this.minangle = toRadians(asFloat(params.get("minangle")));
50 | this.maxangle = toRadians(asFloat(params.get("maxangle")));
51 | this.upspeed = asFloat(params.get("upspeed"));
52 | this.downspeed = asFloat(params.get("downspeed"));
53 |
54 | this.anchorBody = Box2DFactory.createCircle(world, this.cx, this.cy, 0.05f, true);
55 | // joint angle is 0 when flipper is horizontal
56 | // flipper needs to be slightly extended past anchorBody to rotate correctly
57 | float ext = (this.flipperLength > 0) ? -0.05f : +0.05f;
58 | // width larger than 0.12 slows rotation?
59 | this.flipperBody = Box2DFactory.createWall(world, cx + ext, cy - 0.12f, cx + flipperLength, cy + 0.12f, 0f);
60 | flipperBody.setType(BodyDef.BodyType.DynamicBody);
61 | flipperBody.setBullet(true);
62 | flipperBody.getFixtureList().get(0).setDensity(5.0f);
63 |
64 | jointDef = new RevoluteJointDef();
65 | jointDef.initialize(anchorBody, flipperBody, new Vector2(this.cx, this.cy));
66 | jointDef.enableLimit = true;
67 | jointDef.enableMotor = true;
68 | // counterclockwise rotations are positive, so flip angles for flippers extending left
69 | jointDef.lowerAngle = (this.flipperLength > 0) ? this.minangle : -this.maxangle;
70 | jointDef.upperAngle = (this.flipperLength > 0) ? this.maxangle : -this.minangle;
71 | jointDef.maxMotorTorque = 1000f;
72 |
73 | this.joint = (RevoluteJoint)world.createJoint(jointDef);
74 |
75 | flipperBodySet = Collections.singleton(flipperBody);
76 | this.setEffectiveMotorSpeed(-this.downspeed); // force flipper to bottom when field is first created
77 | }
78 |
79 | /** Returns true if the flipper rotates around its right end, which requires negating some values. */
80 | boolean isReversed () {
81 | return (flipperLength < 0);
82 | }
83 |
84 | /** Returns the motor speed of the Box2D joint, normalized to be positive when the flipper is moving up. */
85 | float getEffectiveMotorSpeed () {
86 | float speed = joint.getMotorSpeed();
87 | return (isReversed()) ? -speed : speed;
88 | }
89 |
90 | /** Sets the motor speed of the Box2D joint, positive values move the flipper up. */
91 | void setEffectiveMotorSpeed (float speed) {
92 | if (isReversed()) speed = -speed;
93 | joint.setMotorSpeed(speed);
94 | }
95 |
96 | @Override
97 | public Collection getBodies () {
98 | return flipperBodySet;
99 | }
100 |
101 | @Override
102 | public boolean shouldCallTick () {
103 | return true;
104 | }
105 |
106 | @Override
107 | public void tick (Field field) {
108 | super.tick(field);
109 |
110 | // if angle is at maximum, reduce speed so that the ball won't fly off when it hits
111 | if (getEffectiveMotorSpeed() > 0.5f) {
112 | float topAngle = (isReversed()) ? jointDef.lowerAngle : jointDef.upperAngle;
113 | if (Math.abs(topAngle - joint.getJointAngle()) < 0.05) {
114 | setEffectiveMotorSpeed(0.5f);
115 | }
116 | }
117 | }
118 |
119 | public boolean isFlipperEngaged () {
120 | return getEffectiveMotorSpeed() > 0;
121 | }
122 |
123 | public void setFlipperEngaged (boolean active) {
124 | // only adjust speed if state is changing, so we don't accelerate flipper that's been slowed down in tick()
125 | if (active != this.isFlipperEngaged()) {
126 | float speed = (active) ? upspeed : -downspeed;
127 | setEffectiveMotorSpeed(speed);
128 | }
129 | }
130 |
131 | public float getFlipperLength () {
132 | return flipperLength;
133 | }
134 |
135 | public RevoluteJoint getJoint () {
136 | return joint;
137 | }
138 |
139 | public Body getAnchorBody () {
140 | return anchorBody;
141 | }
142 |
143 | @Override
144 | public void draw (IFieldRenderer renderer) {
145 | // draw single line segment from anchor point
146 | Vector2 position = anchorBody.getPosition();
147 | float angle = joint.getJointAngle();
148 | // HACK: angle can briefly get out of range, always draw between min and max
149 | if (angle < jointDef.lowerAngle) angle = jointDef.lowerAngle;
150 | if (angle > jointDef.upperAngle) angle = jointDef.upperAngle;
151 | float x1 = position.x;
152 | float y1 = position.y;
153 | float x2 = position.x + flipperLength * (float)Math.cos(angle);
154 | float y2 = position.y + flipperLength * (float)Math.sin(angle);
155 |
156 | renderer.drawLine(x1, y1, x2, y2, redColorComponent(0), greenColorComponent(255), blueColorComponent(0));
157 |
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/elements/FieldElement.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy.elements;
3 |
4 | import java.util.Collection;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import com.badlogic.gdx.physics.box2d.Body;
9 | import com.badlogic.gdx.physics.box2d.World;
10 | import com.dozingcatsoftware.bouncy.Field;
11 | import com.dozingcatsoftware.bouncy.IFieldRenderer;
12 |
13 | /** Abstract superclass of all elements in the pinball field, such as walls, bumpers, and flippers.
14 | * @author brian */
15 |
16 | public abstract class FieldElement {
17 |
18 | Map parameters;
19 | World box2dWorld;
20 | String elementID;
21 | int[] color; // 3-element r,g,b values between 0 and 255
22 |
23 | int flashCounter = 0; // when >0, inverts colors (e.g. after being hit by the ball), decrements in tick()
24 | long score = 0;
25 |
26 | // default wall color shared by WallElement, WallArcElement, WallPathElement
27 | static int DEFAULT_WALL_RED = 64;
28 | static int DEFAULT_WALL_GREEN = 64;
29 | static int DEFAULT_WALL_BLUE = 160;
30 |
31 | /** Creates and returns a FieldElement object from the given map of parameters. The default class to instantiate is an argument
32 | * to this method, and can be overridden by the "class" property of the parameter map. Calls the no-argument constructor of the
33 | * default or custom class, and then calls initialize() passing the parameter map and World. */
34 | public static FieldElement createFromParameters (Map params, World world, Class defaultClass) {
35 | try {
36 | FieldElement self = null;
37 | if (params.containsKey("class")) {
38 | // if package not specified, use this package
39 | String className = (String)params.get("class");
40 | if (className.contains("BumperElement")) self = new BumperElement();
41 | if (className.contains("DropTargetGroupElement")) self = new DropTargetGroupElement();
42 | if (className.contains("FlipperElement")) self = new FlipperElement();
43 | if (className.contains("RolloverGroupElement")) self = new RolloverGroupElement();
44 | if (className.contains("SensorElement")) self = new SensorElement();
45 | if (className.contains("WallArcElement")) self = new WallArcElement();
46 | if (className.contains("WallElement")) self = new WallElement();
47 | if (className.contains("WallPathElement")) self = new WallPathElement();
48 | } else {
49 | self = new FlipperElement();
50 | }
51 | self.initialize(params, world);
52 | return self;
53 | } catch (Exception ex) {
54 | throw new RuntimeException(ex);
55 | }
56 | }
57 |
58 | /** Extracts common values from the definition parameter map, and calls finishCreate to allow subclasses to further initialize
59 | * themselves. Subclasses should override finishCreate, and should not override this method. */
60 | public void initialize (Map params, World world) {
61 | this.parameters = params;
62 | this.box2dWorld = world;
63 | this.elementID = (String)params.get("id");
64 |
65 | List colorList = (List)params.get("color");
66 | if (colorList != null) {
67 | this.color = new int[] {colorList.get(0), colorList.get(1), colorList.get(2)};
68 | }
69 |
70 | if (params.containsKey("score")) {
71 | this.score = ((Number)params.get("score")).longValue();
72 | }
73 |
74 | this.finishCreate(params, world);
75 | }
76 |
77 | /** Called after creation to determine if tick() needs to be called after every frame is simulated. Default returns false,
78 | * subclasses must override to return true in order for tick() to be called. This is an optimization to avoid needless method
79 | * calls in the game loop. */
80 | public boolean shouldCallTick () {
81 | return false;
82 | }
83 |
84 | /** Called on every update from Field.tick. Default implementation decrements flash counter if active, subclasses can override
85 | * to perform additional processing, e.g. RolloverGroupElement checking for balls within radius of rollovers. Subclasses should
86 | * call super.tick(field). */
87 | public void tick (Field field) {
88 | if (flashCounter > 0) flashCounter--;
89 | }
90 |
91 | /** Called when the player activates one or more flippers. The default implementation does nothing; subclasses can override. */
92 | public void flipperActivated (Field field) {
93 |
94 | }
95 |
96 | /** Causes the colors returned by red/blue/greenColorComponent methods to be inverted for the given number of frames. This can
97 | * be used to flash an element when it is hit by a ball, see PegElement. */
98 | public void flashForFrames (int frames) {
99 | flashCounter = frames;
100 | }
101 |
102 | /** Must be overridden by subclasses, which should perform any setup required after creation. */
103 | public abstract void finishCreate (Map params, World world);
104 |
105 | /** Must be overridden by subclasses to return a collection of all Box2D bodies which make up this element. */
106 | public abstract Collection getBodies ();
107 |
108 | /** Must be overridden by subclasses to draw the element, using IFieldRenderer methods. */
109 | public abstract void draw (IFieldRenderer renderer);
110 |
111 | /** Called when a ball collides with a Body in this element. The default implementation does nothing (allowing objects to bounce
112 | * off each other normally), subclasses can override (e.g. to apply extra force) */
113 | public void handleCollision (Body ball, Body bodyHit, Field field) {
114 | }
115 |
116 | /** Returns this element's ID as specified in the JSON definition, or null if the ID is not specified. */
117 | public String getElementID () {
118 | return elementID;
119 | }
120 |
121 | /** Returns the parameter map from which this element was created. */
122 | public Map getParameters () {
123 | return parameters;
124 | }
125 |
126 | /** Returns the "score" value for this element. The score is automatically added when the element is hit by a ball, and elements
127 | * may apply scores under other conditions, e.g. RolloverGroupElement adds the score when a ball comes within range of a
128 | * rollover. */
129 | public long getScore () {
130 | return score;
131 | }
132 |
133 | // look in optional "color" parameter, use default value if not present. Invert if flashCounter>0
134 | protected int colorComponent (int index, int defvalue) {
135 | int value = defvalue;
136 | if (this.color != null) value = this.color[index];
137 | return (flashCounter > 0) ? 255 - value : value;
138 | }
139 |
140 | /** Returns the red component of this element's base color, taken from the "color" parameter. If there is no color parameter,
141 | * the default argument is returned. */
142 | protected int redColorComponent (int defvalue) {
143 | return colorComponent(0, defvalue);
144 | }
145 |
146 | /** Returns the green component of this element's base color, taken from the "color" parameter. If there is no color parameter,
147 | * the default argument is returned. */
148 | protected int greenColorComponent (int defvalue) {
149 | return colorComponent(1, defvalue);
150 | }
151 |
152 | /** Returns the blue component of this element's base color, taken from the "color" parameter. If there is no color parameter,
153 | * the default argument is returned. */
154 | protected int blueColorComponent (int defvalue) {
155 | return colorComponent(2, defvalue);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/core/src/com/dozingcatsoftware/bouncy/elements/RolloverGroupElement.java:
--------------------------------------------------------------------------------
1 |
2 | package com.dozingcatsoftware.bouncy.elements;
3 |
4 | import java.util.ArrayList;
5 | import java.util.Collection;
6 | import java.util.Collections;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import com.badlogic.gdx.math.Vector2;
11 | import com.badlogic.gdx.physics.box2d.Body;
12 | import com.badlogic.gdx.physics.box2d.World;
13 | import com.dozingcatsoftware.bouncy.Field;
14 | import com.dozingcatsoftware.bouncy.IFieldRenderer;
15 |
16 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*;
17 |
18 | /** This class represents a collection of rollover elements, such as the rollovers in the top lanes. They are activated (and
19 | * optionally deactivated) when a ball passes over them. Individual rollovers in the group are represented by instances of the
20 | * Rollover nested class, which specify center, radius, and color. Parameters at the collection level control whether the
21 | * rollovers should cycle when flippers are activated, and whether rollovers can toggle from on to off.
22 | * @author brian */
23 |
24 | public class RolloverGroupElement extends FieldElement {
25 |
26 | static class Rollover {
27 | float cx, cy;
28 | float radius;
29 | float radiusSquared; // optimization when computing whether ball is in range
30 | List color;
31 | long score;
32 | float resetDelay;
33 | }
34 |
35 | boolean cycleOnFlipper;
36 | boolean canToggleOff;
37 | boolean ignoreBall;
38 | float defaultRadius;
39 | float defaultResetDelay;
40 | List rollovers = new ArrayList();
41 | List activeRollovers = new ArrayList();
42 |
43 | List rolloversHitOnPreviousTick = new ArrayList();
44 |
45 | @Override
46 | public void finishCreate (Map params, World world) {
47 | this.canToggleOff = Boolean.TRUE.equals(params.get("toggleOff"));
48 | this.cycleOnFlipper = Boolean.TRUE.equals(params.get("cycleOnFlipper"));
49 | this.ignoreBall = Boolean.TRUE.equals(params.get("ignoreBall"));
50 | this.defaultRadius = asFloat(params.get("radius"));
51 | this.defaultResetDelay = asFloat(params.get("reset"));
52 |
53 | List