├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── planewars
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── jmj
│ │ └── planewars
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── jmj
│ │ │ └── planewars
│ │ │ ├── App.kt
│ │ │ ├── activity
│ │ │ └── MainActivity.kt
│ │ │ ├── fly
│ │ │ ├── FlyController.kt
│ │ │ ├── cons
│ │ │ │ └── FlyType.kt
│ │ │ ├── flyfactory
│ │ │ │ └── FlyFactory.kt
│ │ │ ├── flyobject
│ │ │ │ ├── Boom.kt
│ │ │ │ ├── Bullet.kt
│ │ │ │ ├── Fly.kt
│ │ │ │ └── Plane.kt
│ │ │ ├── view
│ │ │ │ ├── BaseView.kt
│ │ │ │ ├── BulletView.kt
│ │ │ │ ├── FireView.kt
│ │ │ │ ├── FlyBoomView.kt
│ │ │ │ ├── MapView.kt
│ │ │ │ ├── PlaneView.kt
│ │ │ │ └── ScrollStarsView.kt
│ │ │ └── viewtools
│ │ │ │ └── FlyFire.kt
│ │ │ └── tools
│ │ │ ├── ExpansionAny.kt
│ │ │ └── ExpansionContext.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ └── plane.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── jmj
│ └── planewars
│ └── ExampleUnitTest.kt
├── build.gradle
├── gif
└── 3A0D5009E4438B045A19CA0ACC6754BF.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | android {
8 | compileSdkVersion 28
9 | buildToolsVersion "29.0.2"
10 | defaultConfig {
11 | applicationId "com.jmj.planewars"
12 | minSdkVersion 21
13 | targetSdkVersion 28
14 | versionCode 1
15 | versionName "1.0"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | implementation fileTree(dir: 'libs', include: ['*.jar'])
28 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
29 | implementation 'androidx.appcompat:appcompat:1.1.0'
30 | implementation 'androidx.core:core-ktx:1.0.2'
31 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
32 | testImplementation 'junit:junit:4.12'
33 | androidTestImplementation 'androidx.test:runner:1.2.0'
34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
35 | }
36 |
--------------------------------------------------------------------------------
/app/planewars:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/planewars
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/jmj/planewars/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.jmj.planewars", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/App.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars
2 |
3 | import android.app.Application
4 |
5 | class App : Application() {
6 | companion object {
7 | private lateinit var instance: App
8 | fun getInstance()= instance
9 | }
10 |
11 | override fun onCreate() {
12 | super.onCreate()
13 | instance = this
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/activity/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.activity
2 |
3 | import android.animation.ValueAnimator
4 | import android.annotation.SuppressLint
5 | import android.content.DialogInterface
6 | import android.os.Bundle
7 | import android.view.View
8 | import androidx.appcompat.app.AlertDialog
9 | import androidx.appcompat.app.AppCompatActivity
10 | import androidx.core.animation.doOnEnd
11 | import com.jmj.planewars.R
12 | import com.jmj.planewars.fly.FlyController
13 | import com.jmj.planewars.fly.cons.FlyType
14 | import kotlinx.android.synthetic.main.activity_main.*
15 |
16 |
17 | class MainActivity : AppCompatActivity() {
18 |
19 | private lateinit var flyController: FlyController
20 | private var killAnim2One: ValueAnimator? = null
21 | private var killAnim2Zero: ValueAnimator? = null
22 | private var lastAnimViewId = 0
23 | private var killCount = 0
24 | private var killBossCount = 0
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | setContentView(R.layout.activity_main)
28 |
29 | flyController = FlyController(this, mapView)
30 |
31 | flyController.onGameOverListener = object : FlyController.OnGameProgressListener {
32 | override fun onGameOver() {
33 | AlertDialog.Builder(this@MainActivity)
34 | .setMessage("游戏结束! 点击确定继续游戏,点击取消退出游戏")
35 | .setNegativeButton("取消") { _: DialogInterface, _: Int ->
36 | finish()
37 | }.setCancelable(false)
38 | .setPositiveButton("确定") { _: DialogInterface, _: Int ->
39 | flyController.reStart()
40 | }.show()
41 | }
42 |
43 | @SuppressLint("SetTextI18n")
44 | override fun onKill(flyType: FlyType) {
45 | super.onKill(flyType)
46 | if (flyType == FlyType.PLANE_GMD) {
47 | killCount++
48 | tv_kill_count.text = "击败 $killCount"
49 | killAnim(tv_kill_count)
50 | } else if (flyType == FlyType.PLANE_BOSS) {
51 | killBossCount++
52 | tv_kill_boss_count.text = "击败 $killBossCount"
53 | killAnim(tv_kill_boss_count)
54 | }
55 | }
56 | }
57 | }
58 |
59 |
60 | private fun killAnim(view: View) {
61 | if (view.id == lastAnimViewId) {
62 | killAnim2One?.cancel()
63 | killAnim2Zero?.cancel()
64 | } else {
65 | lastAnimViewId = view.id
66 | }
67 | killAnim2One = ValueAnimator.ofFloat(view.alpha, 1F).apply {
68 | addUpdateListener {
69 | view.alpha = it.animatedValue as Float
70 | }
71 | duration = 500
72 | doOnEnd {
73 |
74 | killAnim2Zero = ValueAnimator.ofFloat(view.alpha, 0F).apply {
75 | addUpdateListener {
76 | view.alpha = it.animatedValue as Float
77 | }
78 | duration = 1500
79 | start()
80 | }
81 | }
82 | start()
83 | }
84 | }
85 |
86 |
87 | override fun onDestroy() {
88 | super.onDestroy()
89 | flyController.quitGame()
90 | }
91 |
92 | override fun onBackPressed() {
93 |
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/FlyController.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly
2 |
3 | import android.animation.ValueAnimator
4 | import android.annotation.SuppressLint
5 | import android.app.Activity
6 | import android.os.SystemClock
7 | import android.view.MotionEvent
8 | import android.view.View
9 | import android.view.animation.LinearInterpolator
10 | import androidx.core.animation.doOnEnd
11 | import com.jmj.planewars.fly.cons.FlyType
12 | import com.jmj.planewars.fly.flyfactory.FlyFactory
13 | import com.jmj.planewars.fly.flyobject.Fly
14 | import com.jmj.planewars.fly.flyobject.Plane
15 | import com.jmj.planewars.fly.view.FlyBoomView
16 | import com.jmj.planewars.fly.view.MapView
17 | import com.jmj.planewars.fly.view.PlaneView
18 | import com.jmj.planewars.tools.myLog
19 | import java.lang.Math.abs
20 | import java.util.*
21 | import java.util.concurrent.CopyOnWriteArrayList
22 |
23 |
24 | class FlyController(private var activity: Activity, private var mapView: MapView) {
25 | /**
26 | * 我军飞机
27 | */
28 | private var gcdPlanes: CopyOnWriteArrayList = CopyOnWriteArrayList()
29 | /**
30 | * 敌军飞机集合
31 | */
32 | private var gmdPlanes: CopyOnWriteArrayList = CopyOnWriteArrayList()
33 | /**
34 | * 我军子弹集合
35 | */
36 | private var gcdBullets: CopyOnWriteArrayList = CopyOnWriteArrayList()
37 | /**
38 | * 敌军子弹集合
39 | */
40 | private var gmdBullets: CopyOnWriteArrayList = CopyOnWriteArrayList()
41 | /**
42 | * 屏幕内的敌军飞机数量上限
43 | */
44 | private var gmdPlaneCount = 10
45 | /**
46 | * 地图的宽高
47 | */
48 | private var w = 0
49 | private var h = 0
50 |
51 | private var isGameOver = false
52 | private var isQuitGame = false
53 |
54 | private var random = Random()
55 |
56 | var onGameOverListener: OnGameProgressListener? = null
57 |
58 |
59 | init {
60 | /**
61 | * mapView测量完成
62 | */
63 | mapView.onMeasureFinishListener = object : MapView.OnViewLoadFinishListener {
64 | override fun onFinish(w: Int, h: Int) {
65 | this@FlyController.w = w
66 | this@FlyController.h = h
67 | addGcdPlane()
68 | addGmdPlane()
69 | startBossTimer()
70 | startGmdPlaneShotThread()
71 | }
72 | }
73 | }
74 |
75 | /**
76 | * 重新开始游戏
77 | */
78 | fun reStart() {
79 | isGameOver = false
80 | addGcdPlane()
81 | }
82 |
83 |
84 | /**
85 | * 我的飞机自动发射子弹线程
86 | */
87 | private fun startGcdPlaneShotThread() {
88 | Thread {
89 | while (!isGameOver && !isQuitGame) {
90 | activity.runOnUiThread {
91 | if (gcdPlanes.size != 0) {
92 | shot(gcdPlanes[0] as Plane)
93 | }
94 | }
95 |
96 | SystemClock.sleep(200)
97 | }
98 | }.start()
99 | }
100 |
101 | /**
102 | * 敌机自动发射子弹线程
103 | */
104 | private fun startGmdPlaneShotThread() {
105 | Thread {
106 | while (!isQuitGame) {
107 | gmdPlanes.forEach {
108 | activity.runOnUiThread {
109 | if (random.nextInt(3) % 3 == 0 || it.flyType == FlyType.PLANE_BOSS)
110 | shot(it as Plane)
111 | }
112 | }
113 | SystemClock.sleep(2000)
114 |
115 | }
116 | }.start()
117 | }
118 |
119 | /**
120 | * 定时刷boss
121 | */
122 | private fun startBossTimer() {
123 | Timer().schedule(object : TimerTask() {
124 | override fun run() {
125 | activity.runOnUiThread {
126 | createPlane(FlyType.PLANE_BOSS)
127 | }
128 | }
129 | }, (random.nextInt(3) + 1) * 2000.toLong())
130 | }
131 |
132 |
133 | /**
134 | * 添加我的飞机
135 | */
136 | fun addGcdPlane() {
137 | createPlane(FlyType.PLANE_GCD)
138 | startGcdPlaneShotThread()
139 | }
140 |
141 | /**
142 | * 添加敌机
143 | */
144 | private fun addGmdPlane() {
145 | for (i in 0 until gmdPlaneCount) {
146 | createPlane(FlyType.PLANE_GMD)
147 | }
148 | }
149 |
150 |
151 | /**
152 | * 创建飞机并添加到集合
153 | */
154 | private fun createPlane(flyType: FlyType) {
155 | val plane = FlyFactory.getPlane(activity, flyType)!!
156 |
157 | if (flyType == FlyType.PLANE_GCD) {
158 | //我的飞机的位置
159 | plane.x = w / 2F - plane.w / 2
160 | plane.y = h - plane.h.toFloat()
161 | (plane.view as PlaneView).openMask()
162 | openFlyDrag(plane)
163 | } else if (flyType == FlyType.PLANE_GMD) {
164 | //敌机的位置
165 | plane.x = (w - plane.w) * random.nextFloat()
166 | plane.y = -plane.h * 2F
167 | } else {
168 | //boss机的位置
169 | plane.x = (w - plane.w) * random.nextFloat()
170 | plane.y = -plane.h * 2F
171 | moveFlyX(plane)
172 | }
173 |
174 | addFly(plane)
175 |
176 | moveFlyY(plane)
177 | }
178 |
179 |
180 | /**
181 | * 创建爆炸效果
182 | */
183 | private fun createBoom(fly: Fly) {
184 | val boom = FlyFactory.getBoom(activity, fly.flyType)!!
185 | boom.x = (fly.cx - boom.w / 2)
186 | boom.y = (fly.cy - boom.h / 2)
187 | mapView.addFly(boom)
188 |
189 | val flyBoomView = boom.view as FlyBoomView
190 | //爆炸动画结束的时候将其从mapView中删除
191 | flyBoomView.animatorListener = object : FlyBoomView.AnimatorListener {
192 | override fun onAnimationEnd() {
193 | removeFly(boom)
194 | }
195 | }
196 | }
197 |
198 | /**
199 | * 创建子弹
200 | */
201 | private fun createBullet(plane: Plane) {
202 | val bullet = FlyFactory.getBullet(activity, plane.flyType)!!
203 |
204 | bullet.x = (plane.cx - bullet.w / 2)
205 |
206 | bullet.y = (plane.cy - bullet.h / 2)
207 |
208 | addFly(bullet)
209 |
210 | moveFlyY(bullet)
211 | }
212 |
213 | /**
214 | * 发射子弹
215 | */
216 | private fun shot(plane: Plane) {
217 | createBullet(plane)
218 | }
219 |
220 |
221 | /**
222 | * y轴移动Fly
223 | */
224 | private fun moveFlyY(fly: Fly) {
225 | //如果fly已经死亡
226 | if (fly.isBoom) {
227 | return
228 | }
229 |
230 | var start = fly.cy
231 |
232 | var end = when (fly.flyType) {
233 | FlyType.BULLET_GCD -> {
234 | -fly.h * 2
235 | }
236 | FlyType.PLANE_GCD -> {
237 | return
238 | }
239 | FlyType.PLANE_GMD -> {
240 | h + fly.h * 2
241 | }
242 | FlyType.BULLET_GMD -> {
243 | h + fly.h * 2
244 | }
245 | FlyType.PLANE_BOSS -> {
246 | 0
247 | }
248 | else -> {
249 | h + fly.h * 2
250 | }
251 | }
252 |
253 |
254 | ValueAnimator.ofFloat(start, end.toFloat())
255 | .apply {
256 | addUpdateListener {
257 | //如果fly已经死亡
258 | if (fly.isBoom) {
259 | cancel()
260 | return@addUpdateListener
261 | }
262 |
263 | //移动Fly
264 | fly.y = (it.animatedValue as Float)
265 |
266 | //超出屏幕检测
267 | if (checkFlyPosition(fly)) {
268 | selectHitAndBeHit(fly)
269 | }
270 | }
271 | duration = (abs(start - end)).toLong() * fly.speed
272 | interpolator = LinearInterpolator()
273 | start()
274 | }
275 |
276 | }
277 |
278 | /**
279 | * X轴移动Fly
280 | */
281 | private fun moveFlyX(fly: Fly) {
282 | //如果fly已经死亡
283 | if (fly.isBoom) {
284 | return
285 | }
286 |
287 | var start = fly.x
288 | //左移动或者右移动
289 | var end = if (fly.x < w - fly.w) {
290 | w.toFloat() - fly.w
291 | } else {
292 | 0F
293 | }
294 | ValueAnimator.ofFloat(start, end).apply {
295 | addUpdateListener {
296 | //如果fly已经死亡
297 | if (fly.isBoom) {
298 | cancel()
299 | return@addUpdateListener
300 | }
301 | fly.x = it.animatedValue as Float
302 | //超出屏幕检测
303 | if (checkFlyPosition(fly)) {
304 | selectHitAndBeHit(fly)
305 | }
306 | }
307 | duration = (abs(start - end)).toLong() * fly.speed
308 | interpolator = LinearInterpolator()
309 |
310 | doOnEnd {
311 | //递归调用,实现左右不停移动
312 | moveFlyX(fly)
313 | }
314 | start()
315 | }
316 | }
317 |
318 |
319 | /**
320 | * Fly位置检测 如果已经超出屏幕则删除
321 | */
322 | private fun checkFlyPosition(fly: Fly): Boolean {
323 | if (fly.flyType == FlyType.PLANE_BOSS) return true
324 | //如果view已经不再屏幕内了 删除它
325 | if (fly.x + fly.w <= -fly.w ||
326 |
327 | fly.x >= w + fly.w ||
328 |
329 | fly.y + fly.h <= -fly.h ||
330 |
331 | fly.y >= h + fly.h
332 | ) {
333 | removeFly(fly)
334 | return false
335 | }
336 | return true
337 | }
338 |
339 | /**
340 | * 通过flyType来决定选择哪个集合中的元素与它进行碰撞检测
341 | */
342 | private fun selectHitAndBeHit(fly: Fly) {
343 | when (fly.flyType) {
344 | FlyType.BULLET_GCD -> {
345 | //如果是我的子弹,则与敌机集合检测
346 | hitAndBeHit(fly, gmdPlanes)
347 | }
348 | FlyType.BULLET_GMD -> {
349 | //如果是敌机的子弹则与我机检测
350 | hitAndBeHit(fly, gcdPlanes)
351 | }
352 | FlyType.PLANE_GMD -> {
353 | //...
354 | hitAndBeHit(fly, gcdPlanes)
355 | }
356 | FlyType.PLANE_BOSS -> {
357 | //...
358 | hitAndBeHit(fly, gcdPlanes)
359 | }
360 | FlyType.BULLET_BOSS -> {
361 | //...
362 | hitAndBeHit(fly, gcdPlanes)
363 | }
364 | }
365 | }
366 |
367 | /**
368 | * 碰撞和被碰撞检测
369 | */
370 | private fun hitAndBeHit(hitFly: Fly, beHitFlys: CopyOnWriteArrayList) {
371 | for (beHitFly in beHitFlys) {
372 | //碰撞之后跳出循环
373 | if (isCollision(
374 | hitFly.x,
375 | hitFly.y,
376 | hitFly.w.toFloat(),
377 | hitFly.h.toFloat(),
378 | beHitFly.x,
379 | beHitFly.y,
380 | beHitFly.w.toFloat(),
381 | beHitFly.h.toFloat()
382 | )
383 | ) {
384 | sellingHP(hitFly, beHitFly)
385 | break
386 | }
387 | }
388 | }
389 |
390 | /**
391 | * 碰撞扣除HP
392 | */
393 | private fun sellingHP(fly1: Fly, fly2: Fly) {
394 | fly1.HP -= fly2.power
395 | fly2.HP -= fly1.power
396 | isDid(fly1)
397 | isDid(fly2)
398 | }
399 |
400 | /**
401 | * 飞行物是否死亡
402 | */
403 | private fun isDid(fly: Fly) {
404 | if (fly.HP <= 0) {
405 | createBoom(fly)
406 | removeFly(fly)
407 | if (fly.flyType == FlyType.PLANE_GMD || fly.flyType == FlyType.PLANE_BOSS) {
408 | onGameOverListener?.onKill(fly.flyType)
409 | }
410 | if (fly.flyType == FlyType.PLANE_GCD) {
411 | isGameOver = true
412 | onGameOverListener?.onGameOver()
413 | }
414 | }
415 | }
416 |
417 |
418 | /**
419 | * 碰撞检测
420 | */
421 | private fun isCollision(
422 | x1: Float,
423 | y1: Float,
424 | w1: Float,
425 | h1: Float,
426 | x2: Float,
427 | y2: Float,
428 | w2: Float,
429 | h2: Float
430 | ): Boolean {
431 | if (x1 > x2 + w2 || x1 + w1 < x2 || y1 > y2 + h2 || y1 + h1 < y2) {
432 | return false
433 | }
434 | return true
435 | }
436 |
437 |
438 | /**
439 | * 手势拖动我的飞机
440 | */
441 | @SuppressLint("ClickableViewAccessibility")
442 | private fun openFlyDrag(fly: Fly) {
443 | mapView.setOnTouchListener(object : View.OnTouchListener {
444 | var dx = 0
445 | var dy = 0
446 | var mx = 0
447 | var my = 0
448 | override fun onTouch(v: View?, event: MotionEvent?): Boolean {
449 | when (event?.action) {
450 | MotionEvent.ACTION_DOWN -> {
451 | dx = event.x.toInt()
452 | dy = event.y.toInt()
453 | }
454 | MotionEvent.ACTION_MOVE -> {
455 | mx = event.x.toInt()
456 | my = event.y.toInt()
457 | move()
458 | dx = mx
459 | dy = my
460 | }
461 | MotionEvent.ACTION_UP -> {
462 |
463 | }
464 | }
465 | return true
466 | }
467 |
468 | private fun move() {
469 | fly.x += (mx - dx)
470 | fly.y += (my - dy)
471 |
472 | //拖动超出屏幕检测
473 | if (fly.x < 0F) {
474 | fly.x = 0F
475 | }
476 | if (fly.y < 0F) {
477 | fly.y = 0F
478 | }
479 |
480 | if (fly.x + fly.w > w) {
481 | fly.x = w.toFloat() - fly.w
482 | }
483 | if (fly.y + fly.h > h) {
484 | fly.y = h.toFloat() - fly.h
485 | }
486 | }
487 | })
488 | }
489 |
490 |
491 | /**
492 | * 添加Fly到集合和mapView
493 | */
494 | private fun addFly(fly: Fly) {
495 | when (fly.flyType) {
496 | FlyType.BULLET_GCD -> {
497 | gcdBullets.add(fly)
498 | }
499 | FlyType.BULLET_GMD -> {
500 | gmdBullets.add(fly)
501 | }
502 | FlyType.BULLET_BOSS -> {
503 | gmdBullets.add(fly)
504 | }
505 | FlyType.PLANE_BOSS -> {
506 | gmdPlanes.add(fly)
507 | }
508 | FlyType.PLANE_GCD -> {
509 | gcdPlanes.add(fly)
510 | }
511 | FlyType.PLANE_GMD -> {
512 | gmdPlanes.add(fly)
513 | }
514 | }
515 | mapView.addFly(fly)
516 | }
517 |
518 | /**
519 | * 从集合和mapView中删除fly
520 | */
521 | private fun removeFly(fly: Fly) {
522 | when (fly.flyType) {
523 | FlyType.BULLET_GCD -> {
524 | gcdBullets.remove(fly)
525 | }
526 | FlyType.BULLET_GMD -> {
527 | gmdBullets.remove(fly)
528 | }
529 | FlyType.PLANE_GMD -> {
530 | createPlane(FlyType.PLANE_GMD)
531 | gmdPlanes.remove(fly)
532 | }
533 | FlyType.PLANE_GCD -> {
534 | gcdPlanes.remove(fly)
535 | }
536 | FlyType.BULLET_BOSS -> {
537 | gmdBullets.remove(fly)
538 | }
539 | FlyType.PLANE_BOSS -> {
540 | startBossTimer()
541 | gmdPlanes.remove(fly)
542 | }
543 | }
544 | mapView.removeFly(fly)
545 | fly.boom()
546 | }
547 |
548 | fun quitGame() {
549 | isQuitGame = true
550 | }
551 |
552 | interface OnGameProgressListener {
553 | fun onGameOver() {}
554 | fun onKill(flyType: FlyType) {}
555 | fun onStart() {}
556 | fun onPause() {}
557 | }
558 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/cons/FlyType.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.cons
2 |
3 |
4 | enum class FlyType {
5 | PLANE_GCD, //我军飞机
6 | PLANE_GMD, //敌军飞机
7 | PLANE_BOSS, //boss飞机
8 | BULLET_GCD, //我军子弹
9 | BULLET_GMD, //敌军子弹
10 | BULLET_BOSS,//boss子弹
11 | BOOM,
12 | OTHER //预留
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/flyfactory/FlyFactory.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.flyfactory
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.view.View
6 | import com.jmj.planewars.fly.cons.FlyType
7 | import com.jmj.planewars.fly.flyobject.Boom
8 | import com.jmj.planewars.fly.flyobject.Bullet
9 | import com.jmj.planewars.fly.flyobject.Plane
10 | import com.jmj.planewars.fly.view.*
11 | import com.jmj.planewars.tools.dp2px
12 | import java.util.*
13 |
14 | object FlyFactory {
15 | private var random = Random()
16 | /**
17 | * 我的飞机
18 | */
19 | private var gcdPlaneLength = 40 //飞机大小
20 | private var gcdBulletLength = 20 //子弹大小
21 | private var gcdPlaneHP = 10000 //飞机生命值
22 | private var gcdPlanePower = 100 //飞机撞击威力
23 | private var gcdBulletHP = 0 //子弹生命值
24 | private var gcdBulletPower = 100 //子弹威力
25 |
26 | /**
27 | * 敌机
28 | */
29 | private var gmdPlaneLength = 30 //飞机大小
30 | private var gmdBulletLength = 20 //子弹大小
31 | private var gmdPlaneHp = 100 //飞机生命值
32 | private var gmdPlanePower = 100 //飞机撞击威力
33 | private var gmdBulletHP = 0 //子弹生命值
34 | private var gmdBulletPower = 100 //子弹威力
35 |
36 | /**
37 | * BOSS
38 | */
39 | private var bossPlaneLength = 150 //飞机大小
40 | private var bossBulletLength = 60 //子弹大小
41 | private var bossPlaneHp = 5000 //飞机生命值
42 | private var bossPlanePower = 100 //飞机撞击威力
43 | private var bossBulletHP = 0 //子弹生命值
44 | private var bossBulletPower = 100 //子弹威力
45 |
46 |
47 | private var GMD_PLANE_WING = Color.parseColor("#42AFF5")
48 | private var GMD_PLANE_BODY = Color.parseColor("#F06292")
49 | private var GMD_BULLET = GMD_PLANE_BODY
50 |
51 | private var GCD_PLANE_WING = Color.parseColor("#FFC107")
52 | private var GCD_PLANE_BODY = Color.parseColor("#009688")
53 | private var GCD_BULLET = GCD_PLANE_WING
54 |
55 | private var BOSS_PLANE_WING = Color.parseColor("#FF5722")
56 | private var BOSS_PLANE_BODY = Color.parseColor("#00BCD4")
57 | private var BOSS_BULLET = BOSS_PLANE_WING
58 |
59 | /**
60 | * 获取飞机的Fly
61 | */
62 | fun getPlane(context: Context, flyType: FlyType): Plane? {
63 | return when (flyType) {
64 | FlyType.PLANE_GCD -> {
65 | Plane(
66 | getView(context, flyType)!!,
67 | flyType,
68 | dp2px(gcdPlaneLength),
69 | dp2px(gcdPlaneLength),
70 | 1,
71 | gcdPlanePower,
72 | gcdPlaneHP
73 | )
74 | }
75 | FlyType.PLANE_GMD -> {
76 | Plane(
77 | getView(context, flyType)!!,
78 | flyType,
79 | dp2px(gmdPlaneLength),
80 | dp2px(gmdPlaneLength),
81 | random.nextInt(3) + 3,
82 | gmdPlanePower,
83 | gmdPlaneHp
84 | )
85 | }
86 | FlyType.PLANE_BOSS -> {
87 | Plane(
88 | getView(context, flyType)!!,
89 | flyType,
90 | dp2px(bossPlaneLength),
91 | dp2px(bossPlaneLength),
92 | 2,
93 | bossPlanePower,
94 | bossPlaneHp
95 | )
96 | }
97 | else -> null
98 | }
99 | }
100 |
101 | /**
102 | * 获取子弹的Fly
103 | */
104 | fun getBullet(context: Context, flyType: FlyType): Bullet? {
105 | return when (flyType) {
106 | FlyType.PLANE_GCD -> {
107 | Bullet(
108 | getView(context, FlyType.BULLET_GCD)!!,
109 | FlyType.BULLET_GCD,
110 | dp2px(gcdBulletLength),
111 | dp2px(gcdBulletLength),
112 | 1,
113 | gcdBulletPower,
114 | gcdBulletHP
115 | )
116 | }
117 | FlyType.PLANE_GMD -> {
118 | Bullet(
119 | getView(context, FlyType.BULLET_GMD)!!,
120 | FlyType.BULLET_GMD,
121 | dp2px(gmdBulletLength),
122 | dp2px(gmdBulletLength),
123 | random.nextInt(3),
124 | gmdBulletPower,
125 | gmdBulletHP
126 | )
127 | }
128 | FlyType.PLANE_BOSS -> {
129 | Bullet(
130 | getView(context, FlyType.BULLET_BOSS)!!,
131 | FlyType.BULLET_BOSS,
132 | dp2px(bossBulletLength),
133 | dp2px(bossBulletLength),
134 | 1,
135 | bossBulletPower,
136 | bossBulletHP
137 | )
138 | }
139 | else -> {
140 | null
141 | }
142 | }
143 | }
144 |
145 | /**
146 | * 获取爆炸效果的Fly
147 | */
148 | fun getBoom(context: Context, flyType: FlyType): Boom? {
149 | return when (flyType) {
150 | FlyType.PLANE_GMD -> {
151 | Boom(
152 | getBoomView(context, flyType)!!,
153 | FlyType.BOOM,
154 | dp2px(gmdPlaneLength),
155 | dp2px(gmdPlaneLength),
156 | 0
157 | )
158 | }
159 | FlyType.BULLET_GMD -> {
160 | Boom(
161 | getBoomView(context, flyType)!!,
162 | FlyType.BOOM,
163 | dp2px(gmdBulletLength),
164 | dp2px(gmdBulletLength),
165 | 0
166 | )
167 | }
168 |
169 | FlyType.PLANE_GCD -> {
170 | Boom(
171 | getBoomView(context, flyType)!!,
172 | FlyType.BOOM,
173 | dp2px(gcdPlaneLength),
174 | dp2px(gcdBulletLength),
175 | 0
176 | )
177 | }
178 |
179 |
180 | FlyType.BULLET_GCD -> {
181 | Boom(
182 | getBoomView(context, flyType)!!,
183 | FlyType.BOOM,
184 | dp2px(gmdBulletLength),
185 | dp2px(gmdBulletLength),
186 | 0
187 | )
188 | }
189 |
190 | FlyType.PLANE_BOSS -> {
191 | Boom(
192 | getBoomView(context, flyType)!!,
193 | FlyType.BOOM,
194 | dp2px(bossPlaneLength),
195 | dp2px(bossBulletLength),
196 | 0
197 | )
198 | }
199 |
200 | FlyType.BULLET_BOSS -> {
201 | Boom(
202 | getBoomView(context, flyType)!!,
203 | FlyType.BOOM,
204 | dp2px(bossBulletLength),
205 | dp2px(bossBulletLength),
206 | 0
207 | )
208 | }
209 |
210 | else -> {
211 | null
212 | }
213 |
214 | }
215 | }
216 |
217 | /**
218 | * 获取爆炸效果的View
219 | */
220 | private fun getBoomView(context: Context, flyType: FlyType): View? {
221 | return when (flyType) {
222 | FlyType.PLANE_GMD -> {
223 | FlyBoomView(context, GMD_PLANE_WING)
224 | }
225 | FlyType.BULLET_GMD -> {
226 | FlyBoomView(context, GMD_BULLET)
227 | }
228 | FlyType.PLANE_GCD -> {
229 | FlyBoomView(context, GCD_PLANE_WING)
230 | }
231 | FlyType.BULLET_GCD -> {
232 | FlyBoomView(context, GCD_BULLET)
233 | }
234 | FlyType.PLANE_BOSS -> {
235 | FlyBoomView(context, BOSS_PLANE_WING)
236 | }
237 | FlyType.BULLET_BOSS -> {
238 | FlyBoomView(context, BOSS_BULLET)
239 | }
240 | else -> null
241 | }
242 | }
243 |
244 | /**
245 | * 获取飞机或子弹的View
246 | */
247 | private fun getView(context: Context, flyType: FlyType): View? {
248 | return when (flyType) {
249 | FlyType.PLANE_GCD -> {
250 | PlaneView(context, GCD_PLANE_WING, GCD_PLANE_BODY, false)
251 | }
252 | FlyType.PLANE_BOSS -> {
253 | PlaneView(context, BOSS_PLANE_WING, BOSS_PLANE_BODY, true)
254 | }
255 | FlyType.PLANE_GMD -> {
256 | PlaneView(context, GMD_PLANE_WING, GMD_PLANE_BODY, true)
257 | }
258 | FlyType.BULLET_GCD -> {
259 | BulletView(context, GCD_BULLET, false)
260 | }
261 | FlyType.BULLET_GMD -> {
262 | BulletView(context, GMD_BULLET, true)
263 | }
264 | FlyType.BULLET_BOSS -> {
265 | BulletView(context, BOSS_BULLET, true)
266 | }
267 | else -> null
268 | }
269 | }
270 |
271 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/flyobject/Boom.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.flyobject
2 |
3 | import android.view.View
4 | import com.jmj.planewars.fly.cons.FlyType
5 |
6 | class Boom : Fly {
7 | constructor(view: View, flyType: FlyType, w: Int, h: Int, speed: Int) : super(
8 | view,
9 | flyType,
10 | w,
11 | h,
12 | speed,
13 | 0,
14 | 0
15 | )
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/flyobject/Bullet.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.flyobject
2 |
3 | import android.view.View
4 | import com.jmj.planewars.fly.cons.FlyType
5 | import com.jmj.planewars.fly.flyobject.Fly
6 |
7 |
8 | class Bullet : Fly {
9 |
10 | var bulletColor = 0
11 |
12 | constructor(
13 | view: View,
14 | flyType: FlyType,
15 | w: Int,
16 | h: Int,
17 | speed: Int,
18 | power: Int,
19 | HP: Int
20 | ) : super(view, flyType, w, h, speed, power, HP)
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/flyobject/Fly.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.flyobject
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import com.jmj.planewars.fly.cons.FlyType
7 |
8 | abstract class Fly {
9 | //飞行器类型
10 | var flyType: FlyType
11 | //飞行器展示的view
12 | var view: View
13 | //飞行器的的偏移值 xy
14 | var x = 0F
15 | set(value) {
16 | view.x = value
17 | cx = (view.x + w / 2)
18 | field = value
19 | }
20 | var y = 0F
21 | set(value) {
22 | view.y = value
23 | cy = (view.y + h / 2)
24 | field = value
25 | }
26 | //飞行器的宽高
27 | var w = 0
28 | var h = 0
29 | //飞行器的血量
30 | var HP = 100
31 | //飞行器的碰撞威力
32 | var power = 100
33 | //这个飞行器是否已经死亡
34 | var isBoom = false
35 | //飞行器的移动速度 越小越快
36 | var speed = 1
37 | //上下文
38 | var context: Context
39 | //飞行器的中心点
40 | var cx = 0F
41 | var cy = 0F
42 |
43 |
44 |
45 |
46 | constructor(view: View, flyType: FlyType, w: Int, h: Int, speed: Int, power: Int, HP: Int) {
47 | this.view = view
48 | this.flyType = flyType
49 | this.speed = speed
50 | this.w = w
51 | this.h = h
52 | this.power = power
53 | this.HP = HP
54 | this.context = view.context
55 | val layoutParams = ViewGroup.LayoutParams(w, h)
56 | view.layoutParams = layoutParams
57 | }
58 |
59 | fun setFlyPosition(x: Int, y: Int) {
60 | }
61 |
62 | /**
63 | * 死亡
64 | */
65 | fun boom() {
66 | isBoom = true
67 | }
68 |
69 |
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/flyobject/Plane.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.flyobject
2 |
3 | import android.view.View
4 | import com.jmj.planewars.fly.cons.FlyType
5 |
6 | class Plane : Fly {
7 |
8 |
9 | constructor(
10 | view: View,
11 | flyType: FlyType,
12 | w: Int,
13 | h: Int,
14 | speed: Int,
15 | power: Int,
16 | HP: Int
17 | ) : super(view, flyType, w, h, speed, power, HP)
18 |
19 |
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/view/BaseView.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 |
7 | open class BaseView :View{
8 | var w = 0F
9 | var h = 0F
10 | var cx = 0F
11 | var cy = 0F
12 |
13 | constructor(context: Context?) : super(context)
14 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
15 |
16 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
17 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
18 | w = measuredWidth.toFloat()
19 | h = measuredHeight.toFloat()
20 | cx = w / 2
21 | cy = h / 2
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/view/BulletView.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.view
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.util.AttributeSet
6 | import androidx.annotation.ColorInt
7 |
8 | /**
9 | * 子弹
10 | */
11 | class BulletView : FireView {
12 |
13 | /**
14 | * 子弹颜色
15 | */
16 | private var bulletColor = 0
17 |
18 | override fun onDraw(canvas: Canvas?) {
19 | super.onDraw(canvas)
20 | canvas?.let {
21 | paint.color = bulletColor
22 |
23 | it.drawRoundRect(
24 | horizontalMolecule * 2,
25 | 0F,
26 | horizontalMolecule * 3,
27 | verticalMolecule * 4,
28 | 10F,
29 | 10F,
30 | paint
31 | )
32 |
33 |
34 | it.drawRoundRect(
35 | horizontalMolecule,
36 | verticalMolecule * 4 - verticalMolecule,
37 | w - horizontalMolecule,
38 | verticalMolecule * 4,
39 | 10F,
40 | 10F,
41 | paint
42 | )
43 | }
44 | }
45 |
46 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
47 | constructor(context: Context?, @ColorInt bulletColor: Int, isReverse: Boolean) : super(
48 | context,
49 | isReverse
50 | ) {
51 | this.bulletColor = bulletColor
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/view/FireView.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.view
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Color
6 | import android.graphics.Paint
7 | import android.graphics.Rect
8 | import android.util.AttributeSet
9 | import com.jmj.planewars.fly.viewtools.FlyFire
10 | import com.jmj.planewars.tools.dp2px
11 |
12 | /**
13 | * 有火焰效果的基类View
14 | */
15 | abstract class FireView : BaseView {
16 | private var flyFire: FlyFire? = null
17 | private var isReverse = false
18 | var horizontalMolecule = 0F
19 | var verticalMolecule = 0F
20 | var paint = Paint().apply {
21 | isAntiAlias = true
22 | strokeWidth = dp2px(2).toFloat()
23 | color = Color.BLACK
24 | style = Paint.Style.STROKE
25 | }
26 |
27 |
28 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
29 |
30 | constructor(context: Context?, isReverse: Boolean) : super(context) {
31 | this.isReverse = isReverse
32 | }
33 |
34 |
35 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
36 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
37 | computeRect()
38 | elevation = dp2px(10).toFloat()
39 | }
40 |
41 |
42 | override fun onDraw(canvas: Canvas?) {
43 | super.onDraw(canvas)
44 | canvas?.let {
45 | if (isReverse) {
46 | canvas.rotate(180F, w / 2, h / 2)
47 | }
48 | flyFire?.drawParticle(it)
49 | }
50 | }
51 |
52 |
53 | /**
54 | * 计算每块区域的具体位置
55 | */
56 | private fun computeRect() {
57 | var horizontalDenominator = 5
58 | var verticalDenominator = 5
59 |
60 | //横向每份的长度
61 | horizontalMolecule = w / horizontalDenominator
62 | //竖向每份的长度
63 | verticalMolecule = h / verticalDenominator
64 |
65 | var rect = Rect(
66 | (horizontalMolecule * 2).toInt(),
67 | (verticalMolecule * 4).toInt(),
68 | (horizontalMolecule * 3).toInt(),
69 | h.toInt()
70 | )
71 | flyFire = FlyFire(rect, this)
72 | }
73 |
74 |
75 | override fun onDetachedFromWindow() {
76 | super.onDetachedFromWindow()
77 | flyFire?.stopDrawParticle()
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/view/FlyBoomView.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.view
2 |
3 | import android.animation.ValueAnimator
4 | import android.content.Context
5 | import android.graphics.Canvas
6 | import android.graphics.Color
7 | import android.graphics.Paint
8 | import android.graphics.Rect
9 | import android.util.AttributeSet
10 | import android.view.animation.LinearInterpolator
11 | import androidx.annotation.ColorInt
12 | import androidx.core.animation.doOnEnd
13 | import com.jmj.planewars.tools.dp2px
14 | import kotlin.math.pow
15 | import kotlin.math.sqrt
16 |
17 | /**
18 | * 爆炸效果
19 | */
20 | class FlyBoomView : BaseView {
21 | /**
22 | * 方块集合
23 | */
24 | private var rects = ArrayList()
25 | /**
26 | * 爆炸动画时间
27 | */
28 | private var BOOM_DURATION = 500L
29 | /**
30 | * 爆炸动画
31 | */
32 | var animatorListener: AnimatorListener? = null
33 |
34 | private var paint = Paint().apply {
35 | isAntiAlias = true
36 | strokeWidth = dp2px(2).toFloat()
37 | color = Color.RED
38 | style = Paint.Style.FILL
39 | }
40 |
41 | constructor(context: Context?, @ColorInt boomColor: Int ) : super(context) {
42 | paint.color = boomColor
43 | }
44 |
45 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
46 |
47 |
48 | /**
49 | * 所有的方块实际都朝一个方向移动,但是每移动一个旋转30°,造成圆形扩散效果
50 | */
51 | override fun onDraw(canvas: Canvas?) {
52 | super.onDraw(canvas)
53 | for (index in rects.indices) {
54 | canvas!!.drawRect(rects[index], paint)
55 | canvas.rotate(30F, cx, cy)
56 | }
57 |
58 | }
59 |
60 |
61 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
62 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
63 | w = measuredWidth.toFloat()
64 | h = measuredHeight.toFloat()
65 | cx = w / 2
66 | cy = h / 2
67 | computeRect()
68 | boomAnim()
69 | }
70 |
71 | /**
72 | * 移动方块动画
73 | */
74 | private fun boomAnim() {
75 | val animEnd = (sqrt(w.coerceAtLeast(h).toDouble().pow(2.0)) / 2).toInt()
76 | var oldAnimatedValue = 0
77 | ValueAnimator.ofInt(0, (animEnd * 1.5F).toInt()).apply {
78 | addUpdateListener {
79 |
80 | val animatedValue = it.animatedValue as Int
81 | paint.alpha = (255 - (animatedValue * (255F / (animEnd * 1.5F)))).toInt()
82 |
83 | for (index in rects.indices) {
84 | val rect = rects[index]
85 | rect.left += animatedValue - oldAnimatedValue
86 | rect.right += animatedValue - oldAnimatedValue
87 | rect.top += animatedValue - oldAnimatedValue
88 | rect.bottom += animatedValue - oldAnimatedValue
89 | }
90 | oldAnimatedValue = animatedValue
91 | invalidate()
92 | }
93 | interpolator = LinearInterpolator()
94 | duration = BOOM_DURATION
95 | doOnEnd {
96 | animatorListener?.onAnimationEnd()
97 | }
98 | start()
99 | }
100 | }
101 |
102 | /**
103 | * 添加12个方块在View的中心
104 | */
105 | private fun computeRect() {
106 | rects.clear()
107 | if (rects.size == 0) {
108 | var length = w.coerceAtMost(h) / 5
109 | for (i in 0..11) {
110 | rects.add(
111 | Rect(
112 | (cx - length / 2).toInt(),
113 | (cy - length / 2).toInt(),
114 | (cx + length / 2).toInt(),
115 | (cy + length / 2).toInt()
116 | )
117 | )
118 | }
119 | }
120 | }
121 |
122 | interface AnimatorListener {
123 | fun onAnimationEnd()
124 | }
125 |
126 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/view/MapView.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.ViewGroup
6 | import com.jmj.planewars.fly.flyobject.Fly
7 |
8 | /**
9 | * 飞行物的容器
10 | */
11 | class MapView : ViewGroup {
12 | constructor(context: Context?) : super(context)
13 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
14 |
15 | private var w = 0
16 | private var h = 0
17 | private var loadFinish = false
18 | var onMeasureFinishListener: OnViewLoadFinishListener? = null
19 |
20 | init {
21 | setWillNotDraw(true)
22 | }
23 |
24 | /**
25 | * 指定飞行物的位置
26 | */
27 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
28 | for (i in 0 until childCount) {
29 | val child = getChildAt(i)
30 | if (child != null) {
31 | val childW = child.measuredWidth
32 | val childH = child.measuredHeight
33 | child.layout(0, 0, childW, childH)
34 | }
35 |
36 | }
37 | }
38 |
39 | /**
40 | * 测量
41 | */
42 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
43 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
44 | measureChildren(widthMeasureSpec, heightMeasureSpec)
45 | w = measuredWidth
46 | h = measuredHeight
47 | if (!loadFinish) {
48 | onMeasureFinishListener?.onFinish(w, h)
49 | loadFinish = true
50 | }
51 | }
52 |
53 | /**
54 | * 添加飞行物
55 | */
56 | fun addFly(fly: Fly) {
57 | addView(fly.view)
58 |
59 | }
60 |
61 | /**
62 | * 删除飞行物
63 | */
64 | fun removeFly(fly: Fly) {
65 | removeView(fly.view)
66 | }
67 |
68 | interface OnViewLoadFinishListener {
69 | fun onFinish(w: Int, h: Int)
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/view/PlaneView.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.view
2 |
3 | import android.animation.ValueAnimator
4 | import android.content.Context
5 | import android.graphics.Canvas
6 | import android.graphics.Color
7 | import android.graphics.Paint
8 | import androidx.annotation.ColorInt
9 |
10 | /**
11 | * 飞机
12 | */
13 | class PlaneView : FireView {
14 | /**
15 | * 遮罩颜色
16 | */
17 | private var maskColor = 0
18 | /**
19 | * 翅膀颜色
20 | */
21 | private var wingColor = 0
22 | /**
23 | * 躯干颜色
24 | */
25 | private var bodyColor = 0
26 |
27 | private var maskAnim: ValueAnimator? = null
28 |
29 | constructor(
30 | context: Context?, @ColorInt wingColor: Int, @ColorInt bodyColor: Int,
31 | isReverse: Boolean
32 | ) : super(
33 | context,
34 | isReverse
35 | ) {
36 | this.wingColor = wingColor
37 | this.bodyColor = bodyColor
38 | }
39 |
40 |
41 | init {
42 | paint.style = Paint.Style.FILL
43 | }
44 |
45 | override fun onDraw(canvas: Canvas?) {
46 | super.onDraw(canvas)
47 | canvas?.let {
48 | paint.color = bodyColor
49 |
50 | it.drawRoundRect(
51 | horizontalMolecule * 2,
52 | 0F,
53 | horizontalMolecule * 3,
54 | verticalMolecule * 4,
55 | 10F,
56 | 10F,
57 | paint
58 | )
59 | paint.color = wingColor
60 |
61 | it.drawRoundRect(
62 | 0F,
63 | verticalMolecule / 2,
64 | w,
65 | verticalMolecule + verticalMolecule / 2,
66 | 10F,
67 | 10F,
68 | paint
69 | )
70 |
71 | it.drawRoundRect(
72 | horizontalMolecule,
73 | verticalMolecule * 4 - verticalMolecule,
74 | w - horizontalMolecule,
75 | verticalMolecule * 4,
76 | 10F,
77 | 10F,
78 | paint
79 | )
80 |
81 | if (maskAnim != null) {
82 | paint.color = maskColor
83 | it.drawCircle(cx, cy, w.coerceAtMost(h) / 2 * 1.5F, paint)
84 | paint.alpha = 0xFF
85 | }
86 | }
87 | }
88 |
89 | /**
90 | * 开启遮罩
91 | */
92 | fun openMask() {
93 | if (maskAnim == null) {
94 | maskAnim =
95 | ValueAnimator.ofArgb(Color.parseColor("#55FFFFFF"), Color.parseColor("#11FFFFFF"))
96 | .apply {
97 | addUpdateListener {
98 | maskColor = it.animatedValue as Int
99 | invalidate()
100 | }
101 | repeatCount = ValueAnimator.INFINITE
102 | repeatMode = ValueAnimator.REVERSE
103 | duration = 1000
104 | start()
105 | }
106 | }
107 |
108 | }
109 |
110 | /**
111 | * 关闭遮罩
112 | */
113 | fun closeMask() {
114 | maskAnim?.cancel()
115 | maskAnim = null
116 | }
117 |
118 | override fun onDetachedFromWindow() {
119 | super.onDetachedFromWindow()
120 | closeMask()
121 | }
122 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/view/ScrollStarsView.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.view
2 |
3 | import android.animation.ValueAnimator
4 | import android.content.Context
5 | import android.graphics.Canvas
6 | import android.graphics.Color
7 | import android.graphics.Paint
8 | import android.util.AttributeSet
9 | import android.view.animation.LinearInterpolator
10 | import java.util.*
11 | import java.util.concurrent.CopyOnWriteArrayList
12 |
13 | /**
14 | * 滚动的星空背景
15 | */
16 | class ScrollStarsView : BaseView {
17 | /**
18 | * 圆形集合
19 | */
20 | private var circleStars = CopyOnWriteArrayList()
21 | /**
22 | * 圆形移动动画
23 | */
24 | private var moveRectAnim: ValueAnimator? = null
25 |
26 | private var random = Random()
27 | /**
28 | * 圆形直径
29 | */
30 | private var diameter = 0
31 |
32 | private var paint = Paint().apply {
33 | isAntiAlias = true
34 | style = Paint.Style.FILL
35 |
36 | }
37 |
38 | constructor(context: Context?) : super(context)
39 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
40 |
41 |
42 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
43 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
44 | diameter = (w / 3).toInt()
45 | addRect()
46 | moveRect()
47 | }
48 |
49 | /**
50 | * 添加圆形
51 | */
52 | private fun addRect() {
53 | circleStars.add(
54 | CircleStar(
55 | (random.nextInt(4) * diameter).toFloat(),
56 | -diameter.toFloat(),
57 | diameter / 2F * (random.nextFloat() + 0.1F)
58 | )
59 | )
60 | }
61 |
62 | override fun onDraw(canvas: Canvas?) {
63 | super.onDraw(canvas)
64 | canvas?.let { c ->
65 | /**
66 | * 绘制背景矩形
67 | */
68 | paint.color = Color.parseColor("#0F2332")
69 | c.drawRect(0F, 0F, w, h, paint)
70 |
71 | /**
72 | * 绘制圆形
73 | */
74 | paint.color = Color.parseColor("#374060")
75 | circleStars.forEach {
76 | c.drawCircle(it.x, it.y, it.r, paint)
77 | }
78 | }
79 | }
80 |
81 |
82 | /**
83 | * 移动圆形 每走diameter*2的距离 触发一次添加
84 | *
85 | */
86 | private fun moveRect() {
87 | if (moveRectAnim == null) {
88 | var oldAnimateValue = 0
89 | moveRectAnim = ValueAnimator.ofInt(1, diameter * 2)
90 | .apply {
91 | addUpdateListener { it ->
92 | val animateValue = it.animatedValue as Int
93 |
94 | //repeat的时候重置oldAnimateValue
95 | if (oldAnimateValue > animateValue) {
96 | oldAnimateValue = 0
97 | addRect()
98 | }
99 |
100 | circleStars.forEach {
101 | it.y += animateValue - oldAnimateValue
102 | //如果已经走出了屏幕外则删除它
103 | if (it.y > h + diameter) {
104 | circleStars.remove(it)
105 | }
106 | }
107 |
108 | oldAnimateValue = animateValue
109 | invalidate()
110 | }
111 |
112 | repeatCount = ValueAnimator.INFINITE
113 | interpolator = LinearInterpolator()
114 | duration = 2000
115 | start()
116 | }
117 | }
118 | }
119 |
120 | override fun onDetachedFromWindow() {
121 | super.onDetachedFromWindow()
122 | moveRectAnim?.cancel()
123 | moveRectAnim = null
124 | }
125 |
126 | data class CircleStar(var x: Float, var y: Float, var r: Float)
127 |
128 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/fly/viewtools/FlyFire.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.fly.viewtools
2 |
3 | import android.animation.ValueAnimator
4 | import android.graphics.*
5 | import android.view.View
6 | import java.util.concurrent.CopyOnWriteArrayList
7 |
8 | class FlyFire(private var rect: Rect, private var view: View) {
9 | private val particles = CopyOnWriteArrayList()
10 | private var borderWidth = 0
11 | private var paint = Paint()
12 | private var fireAnim: ValueAnimator? = null
13 |
14 | init {
15 | paint.color = Color.LTGRAY
16 | }
17 |
18 | /**
19 | * 绘制粒子
20 | */
21 | private fun draw(canvas: Canvas) {
22 | if (fireAnim == null) {
23 | fireAnim = ValueAnimator.ofFloat(0F, 1F)
24 | .apply {
25 | addUpdateListener {
26 | view.invalidate()
27 | }
28 | repeatCount = ValueAnimator.INFINITE
29 | start()
30 | }
31 | }
32 |
33 | particles.forEach {
34 | var h = rect.bottom - rect.top
35 | var yProgress = it.y - rect.top
36 | //粒子透明度
37 | paint.alpha = (255 - yProgress * (255F / h)).toInt()
38 | canvas.drawRect(
39 | it.x,
40 | it.y,
41 | it.x + borderWidth,
42 | it.y + borderWidth,
43 | paint
44 | )
45 | }
46 | }
47 |
48 | /**
49 | *计算粒子
50 | */
51 | fun drawParticle(canvas: Canvas) {
52 | borderWidth = (rect.right - rect.left) / 3
53 | if (borderWidth < 1) {
54 | borderWidth = 1
55 | }
56 |
57 | if (particles.size == 0) {
58 | addParticle((rect.right - rect.left) / borderWidth / 2 * 2)
59 | }
60 | //改变位置
61 | particles.forEach {
62 | if (it.xv > 0) {
63 | it.xv += 0.005f
64 | } else {
65 | it.xv += -0.005f
66 | }
67 | it.yv = it.yv + 0.2F
68 |
69 | it.x = it.x + it.xv
70 | it.y = it.y + it.yv
71 | }
72 |
73 | particles.forEach {
74 | if (it.y > rect.bottom) {
75 | particles.remove(it)
76 | }
77 | }
78 |
79 | //每绘制一次添加一排粒子
80 | addParticle((rect.right - rect.left) / borderWidth / 2 * 2)
81 | draw(canvas)
82 | }
83 |
84 | /**
85 | * 添加粒子
86 | */
87 | private fun addParticle(count: Int) {
88 | for (i in 0 until count) {
89 | val molecule = (rect.right - rect.left)
90 |
91 | val particle =
92 | Particle(
93 | rect.left + molecule * Math.random().toFloat() - borderWidth / 2,
94 | rect.top.toFloat()
95 | )
96 | particle.xv = 0.5f - 1 * Math.random().toFloat()
97 | particles.add(particle)
98 | }
99 | }
100 |
101 | fun stopDrawParticle() {
102 | fireAnim?.cancel()
103 | }
104 |
105 | data class Particle(var x: Float, var y: Float, var xv: Float = 1F, var yv: Float = 1F)
106 |
107 |
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/tools/ExpansionAny.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.tools
2 |
3 | import android.os.Environment
4 | import android.util.Log
5 | import com.jmj.planewars.App
6 | import com.jmj.planewars.BuildConfig
7 | import java.io.File
8 |
9 |
10 | /**
11 | * 日志打印
12 | */
13 | fun Any.myLog(msg: String, logType: String = "e") {
14 | if (!BuildConfig.DEBUG) {
15 | return
16 | }
17 | val stacks = Throwable().stackTrace
18 | val className = this::class.java.simpleName
19 | val methodName = stacks[1].methodName
20 | val lineNumber = stacks[1].lineNumber
21 |
22 | var logString = "MyLog:($className::$methodName::$lineNumber)"
23 |
24 | when (logType) {
25 | "i" -> {
26 | Log.i(logString, msg)
27 | }
28 | "d" -> {
29 | Log.d(logString, msg)
30 | }
31 | "v" -> {
32 | Log.v(logString, msg)
33 | }
34 | "w" -> {
35 | Log.w(logString, msg)
36 | }
37 | "e" -> {
38 | Log.e(logString, msg)
39 | }
40 | }
41 |
42 | }
43 |
44 | fun Any.myLog(vararg msg: Any) {
45 | if (!BuildConfig.DEBUG) {
46 | return
47 | }
48 | var s = ""
49 | msg.forEach {
50 | s += "$it,"
51 | }
52 | myLog(s)
53 | }
54 |
55 |
56 | fun Any.dp2px(dp: Int): Int {
57 | var scale = App.getInstance().resources.displayMetrics.density
58 | return return ((dp * scale + 0.5f).toInt())
59 | }
60 |
61 | fun Any.px2dp(px: Int): Int {
62 | var scale = App.getInstance().resources.displayMetrics.density
63 | return return ((px / scale + 0.5f).toInt())
64 | }
65 |
66 | /**
67 | * 创建文件路径
68 | */
69 | fun Any.createFile(fileName: String): File {
70 | var dir =
71 | File(Environment.getExternalStorageDirectory(), "Feijidazhan")
72 | if (!dir.exists()) {
73 | dir.mkdirs()
74 | }
75 | return File(dir, fileName)
76 | }
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jmj/planewars/tools/ExpansionContext.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars.tools
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.annotation.StringRes
6 |
7 |
8 | var SHARE_FILE_NAME=""
9 |
10 | fun Context.showToast(msg: String) {
11 | if (msg.isNotEmpty()) {
12 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
13 | }
14 | }
15 |
16 | fun Context.showToast(@StringRes msg: Int) {
17 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
18 | }
19 |
20 | /**
21 | * SharedPreferences
22 | */
23 | fun Context.putStringToShared(key: String, value: String?) {
24 | getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE)
25 | .edit()
26 | .putString(key, value)
27 | .apply()
28 | }
29 |
30 | fun Context.getStringByShared(key: String): String? {
31 | return getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE)
32 | .getString(key, "")
33 | }
34 |
35 | fun Context.putIntToShared(key: String, value: Int) {
36 | getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE)
37 | .edit()
38 | .putInt(key, value)
39 | .apply()
40 | }
41 |
42 | fun Context.getIntByShared(key: String): Int {
43 | return getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE)
44 | .getInt(key, -1)
45 | }
46 |
47 | fun Context.putBooleanToShared(key: String, value: Boolean) {
48 | getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE)
49 | .edit()
50 | .putBoolean(key, value)
51 | .apply()
52 | }
53 |
54 | fun Context.getBooleanByShared(key: String): Boolean {
55 | return getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE)
56 | .getBoolean(key, false)
57 | }
58 |
59 |
60 |
61 | fun Context.dp2px(dp: Int): Int {
62 | var scale = this.resources.displayMetrics.density
63 | return return ((dp * scale + 0.5f).toInt())
64 | }
65 |
66 | fun Context.px2dp(px: Int): Int {
67 | var scale = this.resources.displayMetrics.density
68 | return return ((px / scale + 0.5f).toInt())
69 | }
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/plane.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
21 |
22 |
23 |
36 |
37 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #0F2332
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PlaneWars
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/jmj/planewars/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.jmj.planewars
2 |
3 | import android.graphics.Rect
4 | import org.junit.Test
5 |
6 | import org.junit.Assert.*
7 | import kotlin.math.sqrt
8 | import kotlin.random.Random
9 |
10 | /**
11 | * Example local unit test, which will execute on the development machine (host).
12 | *
13 | * See [testing documentation](http://d.android.com/tools/testing).
14 | */
15 | class ExampleUnitTest {
16 | @Test
17 | fun addition_isCorrect() {
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.50'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.5.0'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url 'https://jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gif/3A0D5009E4438B045A19CA0ACC6754BF.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/gif/3A0D5009E4438B045A19CA0ACC6754BF.gif
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Feb 23 15:34:29 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
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 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='PlaneWars'
3 |
--------------------------------------------------------------------------------