diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/src/com/persesgames/sound/Music.kt b/src/com/persesgames/sound/Music.kt new file mode 100644 index 0000000..88a531d --- /dev/null +++ b/src/com/persesgames/sound/Music.kt @@ -0,0 +1,49 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 13:02 + */ + +object Music { + val playing: MutableSet = HashSet() + + fun load(url: String): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + + return audio; + } + + fun play(url: String, volume: Double = 0.75, looping: Boolean = false): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + audio.volume = volume + audio.play() + + audio.on("ended", true, { + if (looping) { + audio.currentTime = 0.0 + audio.play() + } else { + println("REMOVING: $audio") + audio.remove() + playing.remove(audio) + } + }) + + return audio + } + + fun stopAll() { + + } +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/src/com/persesgames/sound/Music.kt b/src/com/persesgames/sound/Music.kt new file mode 100644 index 0000000..88a531d --- /dev/null +++ b/src/com/persesgames/sound/Music.kt @@ -0,0 +1,49 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 13:02 + */ + +object Music { + val playing: MutableSet = HashSet() + + fun load(url: String): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + + return audio; + } + + fun play(url: String, volume: Double = 0.75, looping: Boolean = false): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + audio.volume = volume + audio.play() + + audio.on("ended", true, { + if (looping) { + audio.currentTime = 0.0 + audio.play() + } else { + println("REMOVING: $audio") + audio.remove() + playing.remove(audio) + } + }) + + return audio + } + + fun stopAll() { + + } +} diff --git a/src/com/persesgames/sound/Sounds.kt b/src/com/persesgames/sound/Sounds.kt new file mode 100644 index 0000000..8d8c549 --- /dev/null +++ b/src/com/persesgames/sound/Sounds.kt @@ -0,0 +1,62 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:34 + */ + +class Sound(val name:String, val url: String, val volume: Double = 0.75, val numberOfChannels: Int) { + var channels: Array + var nextChannel: Int = 0 + + init { + println("CREATING: $name") + channels = Array(numberOfChannels, { document.createElement("audio") as HTMLAudioElement }) + + for (audio in channels) { + audio.src = url + audio.pause() + audio.load() + audio.volume = volume + } + } + + fun play() { + println("PLAYING: $name - $nextChannel") + channels[nextChannel].currentTime = 0.0 + channels[nextChannel].play() + + nextChannel = (nextChannel + 1) % channels.size + } + + fun pause() { + for (audio in channels) { + audio.pause() + } + } +} + +object Sounds { + val sounds: MutableMap = HashMap() + + fun load(name: String, url: String, volume: Double = 0.75, channels: Int = 1 ) { + sounds.put(name, Sound(name, url, volume, channels)) + } + + fun play(name: String, volume: Float = 0.75f) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.play() + } + + fun pause(name: String) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.pause() + } +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/src/com/persesgames/sound/Music.kt b/src/com/persesgames/sound/Music.kt new file mode 100644 index 0000000..88a531d --- /dev/null +++ b/src/com/persesgames/sound/Music.kt @@ -0,0 +1,49 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 13:02 + */ + +object Music { + val playing: MutableSet = HashSet() + + fun load(url: String): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + + return audio; + } + + fun play(url: String, volume: Double = 0.75, looping: Boolean = false): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + audio.volume = volume + audio.play() + + audio.on("ended", true, { + if (looping) { + audio.currentTime = 0.0 + audio.play() + } else { + println("REMOVING: $audio") + audio.remove() + playing.remove(audio) + } + }) + + return audio + } + + fun stopAll() { + + } +} diff --git a/src/com/persesgames/sound/Sounds.kt b/src/com/persesgames/sound/Sounds.kt new file mode 100644 index 0000000..8d8c549 --- /dev/null +++ b/src/com/persesgames/sound/Sounds.kt @@ -0,0 +1,62 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:34 + */ + +class Sound(val name:String, val url: String, val volume: Double = 0.75, val numberOfChannels: Int) { + var channels: Array + var nextChannel: Int = 0 + + init { + println("CREATING: $name") + channels = Array(numberOfChannels, { document.createElement("audio") as HTMLAudioElement }) + + for (audio in channels) { + audio.src = url + audio.pause() + audio.load() + audio.volume = volume + } + } + + fun play() { + println("PLAYING: $name - $nextChannel") + channels[nextChannel].currentTime = 0.0 + channels[nextChannel].play() + + nextChannel = (nextChannel + 1) % channels.size + } + + fun pause() { + for (audio in channels) { + audio.pause() + } + } +} + +object Sounds { + val sounds: MutableMap = HashMap() + + fun load(name: String, url: String, volume: Double = 0.75, channels: Int = 1 ) { + sounds.put(name, Sound(name, url, volume, channels)) + } + + fun play(name: String, volume: Float = 0.75f) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.play() + } + + fun pause(name: String) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.pause() + } +} diff --git a/src/com/persesgames/sprite/SpriteBatch.kt b/src/com/persesgames/sprite/SpriteBatch.kt new file mode 100644 index 0000000..8f72f31 --- /dev/null +++ b/src/com/persesgames/sprite/SpriteBatch.kt @@ -0,0 +1,26 @@ +package com.persesgames.sprite + +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures + +/** + * User: rnentjes + * Date: 20-4-16 + * Time: 13:48 + */ + +class Sprite(val textureName: String) { + val texture: Texture by lazy { Textures.get(textureName) } +} + +class SpriteBatch { + + fun draw(sprite: Sprite, x: Float, y: Float, scale: Float = 1f, rotation: Float = 0f) { + sprite.texture.queueDraw(x, y, scale, rotation) + } + + fun render() { + Textures.render() + } + +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/src/com/persesgames/sound/Music.kt b/src/com/persesgames/sound/Music.kt new file mode 100644 index 0000000..88a531d --- /dev/null +++ b/src/com/persesgames/sound/Music.kt @@ -0,0 +1,49 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 13:02 + */ + +object Music { + val playing: MutableSet = HashSet() + + fun load(url: String): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + + return audio; + } + + fun play(url: String, volume: Double = 0.75, looping: Boolean = false): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + audio.volume = volume + audio.play() + + audio.on("ended", true, { + if (looping) { + audio.currentTime = 0.0 + audio.play() + } else { + println("REMOVING: $audio") + audio.remove() + playing.remove(audio) + } + }) + + return audio + } + + fun stopAll() { + + } +} diff --git a/src/com/persesgames/sound/Sounds.kt b/src/com/persesgames/sound/Sounds.kt new file mode 100644 index 0000000..8d8c549 --- /dev/null +++ b/src/com/persesgames/sound/Sounds.kt @@ -0,0 +1,62 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:34 + */ + +class Sound(val name:String, val url: String, val volume: Double = 0.75, val numberOfChannels: Int) { + var channels: Array + var nextChannel: Int = 0 + + init { + println("CREATING: $name") + channels = Array(numberOfChannels, { document.createElement("audio") as HTMLAudioElement }) + + for (audio in channels) { + audio.src = url + audio.pause() + audio.load() + audio.volume = volume + } + } + + fun play() { + println("PLAYING: $name - $nextChannel") + channels[nextChannel].currentTime = 0.0 + channels[nextChannel].play() + + nextChannel = (nextChannel + 1) % channels.size + } + + fun pause() { + for (audio in channels) { + audio.pause() + } + } +} + +object Sounds { + val sounds: MutableMap = HashMap() + + fun load(name: String, url: String, volume: Double = 0.75, channels: Int = 1 ) { + sounds.put(name, Sound(name, url, volume, channels)) + } + + fun play(name: String, volume: Float = 0.75f) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.play() + } + + fun pause(name: String) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.pause() + } +} diff --git a/src/com/persesgames/sprite/SpriteBatch.kt b/src/com/persesgames/sprite/SpriteBatch.kt new file mode 100644 index 0000000..8f72f31 --- /dev/null +++ b/src/com/persesgames/sprite/SpriteBatch.kt @@ -0,0 +1,26 @@ +package com.persesgames.sprite + +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures + +/** + * User: rnentjes + * Date: 20-4-16 + * Time: 13:48 + */ + +class Sprite(val textureName: String) { + val texture: Texture by lazy { Textures.get(textureName) } +} + +class SpriteBatch { + + fun draw(sprite: Sprite, x: Float, y: Float, scale: Float = 1f, rotation: Float = 0f) { + sprite.texture.queueDraw(x, y, scale, rotation) + } + + fun render() { + Textures.render() + } + +} diff --git a/src/com/persesgames/text/Texts.kt b/src/com/persesgames/text/Texts.kt new file mode 100644 index 0000000..b0e2a17 --- /dev/null +++ b/src/com/persesgames/text/Texts.kt @@ -0,0 +1,30 @@ +package com.persesgames.text + +import com.persesgames.game.Game + +/** + * Created by rnentjes on 16-5-16. + */ + +object Texts { + + fun drawText(x: Float, y: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + var yy = y + if (yy < 0) { + yy += Game.view.height + } + Game.html.canvas2d.fillStyle = fillStyle + Game.html.canvas2d.font = font + Game.html.canvas2d.fillText(message, x.toDouble(), yy.toDouble()) + } + + fun drawLeftTop(left: Float, top: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + drawText( + Game.view.gameToScreenCoordX(-Game.view.width / 2f + left), + Game.view.gameToScreenCoordY(Game.view.height / 2f - top), + message, + font, + fillStyle + ) + } +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/src/com/persesgames/sound/Music.kt b/src/com/persesgames/sound/Music.kt new file mode 100644 index 0000000..88a531d --- /dev/null +++ b/src/com/persesgames/sound/Music.kt @@ -0,0 +1,49 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 13:02 + */ + +object Music { + val playing: MutableSet = HashSet() + + fun load(url: String): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + + return audio; + } + + fun play(url: String, volume: Double = 0.75, looping: Boolean = false): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + audio.volume = volume + audio.play() + + audio.on("ended", true, { + if (looping) { + audio.currentTime = 0.0 + audio.play() + } else { + println("REMOVING: $audio") + audio.remove() + playing.remove(audio) + } + }) + + return audio + } + + fun stopAll() { + + } +} diff --git a/src/com/persesgames/sound/Sounds.kt b/src/com/persesgames/sound/Sounds.kt new file mode 100644 index 0000000..8d8c549 --- /dev/null +++ b/src/com/persesgames/sound/Sounds.kt @@ -0,0 +1,62 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:34 + */ + +class Sound(val name:String, val url: String, val volume: Double = 0.75, val numberOfChannels: Int) { + var channels: Array + var nextChannel: Int = 0 + + init { + println("CREATING: $name") + channels = Array(numberOfChannels, { document.createElement("audio") as HTMLAudioElement }) + + for (audio in channels) { + audio.src = url + audio.pause() + audio.load() + audio.volume = volume + } + } + + fun play() { + println("PLAYING: $name - $nextChannel") + channels[nextChannel].currentTime = 0.0 + channels[nextChannel].play() + + nextChannel = (nextChannel + 1) % channels.size + } + + fun pause() { + for (audio in channels) { + audio.pause() + } + } +} + +object Sounds { + val sounds: MutableMap = HashMap() + + fun load(name: String, url: String, volume: Double = 0.75, channels: Int = 1 ) { + sounds.put(name, Sound(name, url, volume, channels)) + } + + fun play(name: String, volume: Float = 0.75f) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.play() + } + + fun pause(name: String) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.pause() + } +} diff --git a/src/com/persesgames/sprite/SpriteBatch.kt b/src/com/persesgames/sprite/SpriteBatch.kt new file mode 100644 index 0000000..8f72f31 --- /dev/null +++ b/src/com/persesgames/sprite/SpriteBatch.kt @@ -0,0 +1,26 @@ +package com.persesgames.sprite + +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures + +/** + * User: rnentjes + * Date: 20-4-16 + * Time: 13:48 + */ + +class Sprite(val textureName: String) { + val texture: Texture by lazy { Textures.get(textureName) } +} + +class SpriteBatch { + + fun draw(sprite: Sprite, x: Float, y: Float, scale: Float = 1f, rotation: Float = 0f) { + sprite.texture.queueDraw(x, y, scale, rotation) + } + + fun render() { + Textures.render() + } + +} diff --git a/src/com/persesgames/text/Texts.kt b/src/com/persesgames/text/Texts.kt new file mode 100644 index 0000000..b0e2a17 --- /dev/null +++ b/src/com/persesgames/text/Texts.kt @@ -0,0 +1,30 @@ +package com.persesgames.text + +import com.persesgames.game.Game + +/** + * Created by rnentjes on 16-5-16. + */ + +object Texts { + + fun drawText(x: Float, y: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + var yy = y + if (yy < 0) { + yy += Game.view.height + } + Game.html.canvas2d.fillStyle = fillStyle + Game.html.canvas2d.font = font + Game.html.canvas2d.fillText(message, x.toDouble(), yy.toDouble()) + } + + fun drawLeftTop(left: Float, top: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + drawText( + Game.view.gameToScreenCoordX(-Game.view.width / 2f + left), + Game.view.gameToScreenCoordY(Game.view.height / 2f - top), + message, + font, + fillStyle + ) + } +} diff --git a/src/com/persesgames/texture/Sprites.kt b/src/com/persesgames/texture/Sprites.kt new file mode 100644 index 0000000..1b0fd0a --- /dev/null +++ b/src/com/persesgames/texture/Sprites.kt @@ -0,0 +1,7 @@ +package com.persesgames.texture + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:44 + */ diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/src/com/persesgames/sound/Music.kt b/src/com/persesgames/sound/Music.kt new file mode 100644 index 0000000..88a531d --- /dev/null +++ b/src/com/persesgames/sound/Music.kt @@ -0,0 +1,49 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 13:02 + */ + +object Music { + val playing: MutableSet = HashSet() + + fun load(url: String): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + + return audio; + } + + fun play(url: String, volume: Double = 0.75, looping: Boolean = false): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + audio.volume = volume + audio.play() + + audio.on("ended", true, { + if (looping) { + audio.currentTime = 0.0 + audio.play() + } else { + println("REMOVING: $audio") + audio.remove() + playing.remove(audio) + } + }) + + return audio + } + + fun stopAll() { + + } +} diff --git a/src/com/persesgames/sound/Sounds.kt b/src/com/persesgames/sound/Sounds.kt new file mode 100644 index 0000000..8d8c549 --- /dev/null +++ b/src/com/persesgames/sound/Sounds.kt @@ -0,0 +1,62 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:34 + */ + +class Sound(val name:String, val url: String, val volume: Double = 0.75, val numberOfChannels: Int) { + var channels: Array + var nextChannel: Int = 0 + + init { + println("CREATING: $name") + channels = Array(numberOfChannels, { document.createElement("audio") as HTMLAudioElement }) + + for (audio in channels) { + audio.src = url + audio.pause() + audio.load() + audio.volume = volume + } + } + + fun play() { + println("PLAYING: $name - $nextChannel") + channels[nextChannel].currentTime = 0.0 + channels[nextChannel].play() + + nextChannel = (nextChannel + 1) % channels.size + } + + fun pause() { + for (audio in channels) { + audio.pause() + } + } +} + +object Sounds { + val sounds: MutableMap = HashMap() + + fun load(name: String, url: String, volume: Double = 0.75, channels: Int = 1 ) { + sounds.put(name, Sound(name, url, volume, channels)) + } + + fun play(name: String, volume: Float = 0.75f) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.play() + } + + fun pause(name: String) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.pause() + } +} diff --git a/src/com/persesgames/sprite/SpriteBatch.kt b/src/com/persesgames/sprite/SpriteBatch.kt new file mode 100644 index 0000000..8f72f31 --- /dev/null +++ b/src/com/persesgames/sprite/SpriteBatch.kt @@ -0,0 +1,26 @@ +package com.persesgames.sprite + +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures + +/** + * User: rnentjes + * Date: 20-4-16 + * Time: 13:48 + */ + +class Sprite(val textureName: String) { + val texture: Texture by lazy { Textures.get(textureName) } +} + +class SpriteBatch { + + fun draw(sprite: Sprite, x: Float, y: Float, scale: Float = 1f, rotation: Float = 0f) { + sprite.texture.queueDraw(x, y, scale, rotation) + } + + fun render() { + Textures.render() + } + +} diff --git a/src/com/persesgames/text/Texts.kt b/src/com/persesgames/text/Texts.kt new file mode 100644 index 0000000..b0e2a17 --- /dev/null +++ b/src/com/persesgames/text/Texts.kt @@ -0,0 +1,30 @@ +package com.persesgames.text + +import com.persesgames.game.Game + +/** + * Created by rnentjes on 16-5-16. + */ + +object Texts { + + fun drawText(x: Float, y: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + var yy = y + if (yy < 0) { + yy += Game.view.height + } + Game.html.canvas2d.fillStyle = fillStyle + Game.html.canvas2d.font = font + Game.html.canvas2d.fillText(message, x.toDouble(), yy.toDouble()) + } + + fun drawLeftTop(left: Float, top: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + drawText( + Game.view.gameToScreenCoordX(-Game.view.width / 2f + left), + Game.view.gameToScreenCoordY(Game.view.height / 2f - top), + message, + font, + fillStyle + ) + } +} diff --git a/src/com/persesgames/texture/Sprites.kt b/src/com/persesgames/texture/Sprites.kt new file mode 100644 index 0000000..1b0fd0a --- /dev/null +++ b/src/com/persesgames/texture/Sprites.kt @@ -0,0 +1,7 @@ +package com.persesgames.texture + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:44 + */ diff --git a/src/com/persesgames/texture/Textures.kt b/src/com/persesgames/texture/Textures.kt new file mode 100644 index 0000000..24840b6 --- /dev/null +++ b/src/com/persesgames/texture/Textures.kt @@ -0,0 +1,246 @@ +package com.persesgames.texture + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.map.tiled.MapTileset +import com.persesgames.math.Matrix4 +import com.persesgames.shader.ShaderProgram +import com.persesgames.shader.ShaderProgramMesh +import com.persesgames.shader.VertextAttributeInfo +import org.khronos.webgl.WebGLRenderingContext +import org.khronos.webgl.WebGLTexture +import org.w3c.dom.HTMLImageElement +import org.w3c.fetch.Request +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 14:52 + */ + +private val vertexShaderSource = """ + attribute vec2 a_position; + attribute vec2 a_boundingBox; + attribute vec2 a_texCoord; + attribute float a_scale; + attribute float a_rotation; + + uniform mat4 u_projectionView; + + varying vec2 v_textCoord; + + mat4 scale(float scale) { + return mat4( + vec4(scale, 0.0, 0.0, 0.0), + vec4(0.0, scale, 0.0, 0.0), + vec4(0.0, 0.0, scale, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + } + + mat4 rotateZ(float angle) { + return mat4( + vec4(cos(angle), sin(angle), 0.0, 0.0), + vec4(-sin(angle), cos(angle), 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + } + + void main(void) { + v_textCoord = a_texCoord; + + vec4 scaledBox = vec4(a_boundingBox, 1.0, 1.0) * scale(a_scale) * rotateZ(a_rotation); + + gl_Position = u_projectionView * vec4(a_position + scaledBox.xy, 1.0, 1.0); + } +""" + +private val fragmentShaderSource = """ + precision mediump float; + + uniform sampler2D u_sampler; + + varying vec2 v_textCoord; + + void main(void) { + gl_FragColor = texture2D(u_sampler, v_textCoord); + } +""" + +class TextureData( + val vMatrix: Matrix4, + val texture: WebGLTexture +) + +class Texture( + val glTexture: WebGLTexture, + val shaderProgram: ShaderProgram, + val width: Int, + val height: Int +) { + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) + val left = -width / 2f + val right = width / 2f + val bottom = -height / 2f + val top = height / 2f + + fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { + shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) + shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) + shaderProgramMesh.queue( x, y, right, top, 1f, 1f, scale, rotation) + shaderProgramMesh.queue( x, y, right, top, 1f, 1f, scale, rotation) + shaderProgramMesh.queue( x, y, right, bottom, 1f, 0f, scale, rotation) + shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) + + if (shaderProgramMesh.remaining() < 36) { + render() + } + } + + fun queueTileDraw(x: Float, y: Float, tcLeft: Float, tcTop: Float, tcRight: Float, tcBottom: Float, scale: Float) { + shaderProgramMesh.queue( x, y, left, bottom, tcLeft, tcBottom, scale, 0f) + shaderProgramMesh.queue( x, y, left, top, tcLeft, tcTop, scale, 0f) + shaderProgramMesh.queue( x, y, right, top, tcRight, tcTop, scale, 0f) + shaderProgramMesh.queue( x, y, right, top, tcRight, tcTop, scale, 0f) + shaderProgramMesh.queue( x, y, right, bottom, tcRight, tcBottom, scale, 0f) + shaderProgramMesh.queue( x, y, left, bottom, tcLeft, tcBottom, scale, 0f) + + if (shaderProgramMesh.remaining() < 36) { + render() + } + } + + fun render() { + Game.gl().activeTexture(WebGLRenderingContext.TEXTURE0) + Game.gl().bindTexture(WebGLRenderingContext.TEXTURE_2D, glTexture) + + shaderProgramMesh.render(TextureData(Game.view.vMatrix, glTexture)) + } +} + +/* +{ + "frame": {"x":921,"y":1,"w":182,"h":103}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":4,"w":182,"h":103}, + "sourceSize": {"w":190,"h":110}, + "pivot": {"x":0.5,"y":0.5} +}, +*/ + +class Rect(val x: Int, val y: Int, val w: Int, val h: Int) +class Size(val w: Int, val h: Int) +class Pivot(val x: Double, val y: Double) + +class SpriteSheetData( + val frame: Rect, + val rotated: Boolean, + val trimmed: Boolean, + val spriteSourceSize: Rect, + val sourceSize: Size, + val pivot: Pivot + ) + +class SpriteSheet( + val glTexture: WebGLTexture, + val shaderProgram: ShaderProgram, + val data: Map +) { + +} + +object Textures { + var textures = HashMap() + var startedLoading = 0 + var loaded = 0 + val shaderProgram: ShaderProgram + + init { + val setter = { program: ShaderProgram, data: TextureData -> + //program.webgl.activeTexture(WebGLRenderingContext.TEXTURE0); + //program.webgl.bindTexture(WebGLRenderingContext.TEXTURE_2D, data.texture); + + program.setUniform1i("u_sampler", 0) + program.setUniformMatrix4fv("u_projectionView", Game.view.vMatrix.getFloat32Array()) + } + + val vainfo = arrayOf( + VertextAttributeInfo("a_position", 2), + VertextAttributeInfo("a_boundingBox", 2), + VertextAttributeInfo("a_texCoord", 2), + VertextAttributeInfo("a_scale", 1), + VertextAttributeInfo("a_rotation", 1) + ) + + shaderProgram = ShaderProgram(Game.gl(), WebGLRenderingContext.TRIANGLES, vertexShaderSource, fragmentShaderSource, vainfo, setter) + } + + fun loadSpriteSheet(name: String) { + //val data = Request(name).json() + + //println(data) + } + + fun load(name: String, filename: String) { + val gl = Game.gl() + + startedLoading++ + + val webGlTexture = gl.createTexture() + if (webGlTexture != null) { + val image = document.createElement("img") as HTMLImageElement + image.onload = { + textureLoaded(webGlTexture, image) + val texture = Texture(webGlTexture, shaderProgram, image.width, image.height) + + textures.put(name, texture) + + loaded++ + println("loaded texture $loaded/$startedLoading ${ready()}") + } + image.src = filename + } else { + throw IllegalStateException("Couldn't create webgl texture!") + } + } + + fun load(mapTileSet: MapTileset) { + + } + + fun textureLoaded(texture: WebGLTexture, image: HTMLImageElement) { + val gl = Game.gl() + + gl.bindTexture(WebGLRenderingContext.TEXTURE_2D, texture) + gl.pixelStorei(WebGLRenderingContext.UNPACK_FLIP_Y_WEBGL, 1) // second argument must be an int + gl.texImage2D(WebGLRenderingContext.TEXTURE_2D, 0, WebGLRenderingContext.RGBA, WebGLRenderingContext.RGBA, WebGLRenderingContext.UNSIGNED_BYTE, image) + if (Game.view.drawMode == DrawMode.NEAREST) { + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MAG_FILTER, WebGLRenderingContext.NEAREST) + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MIN_FILTER, WebGLRenderingContext.NEAREST) + } else { + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MAG_FILTER, WebGLRenderingContext.LINEAR) + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MIN_FILTER, WebGLRenderingContext.LINEAR) + } + gl.bindTexture(WebGLRenderingContext.TEXTURE_2D, null) + } + + fun ready() = loaded == startedLoading + + fun has(name: String) = textures[name] != null + fun get(name: String) = textures[name] ?: throw IllegalArgumentException("Texture with name $name is not loaded!") + + fun clear() { + // delete and unbind all textures... + } + + fun render() { + for ((key, value) in textures) { + value.render() + } + } + +} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ac7e0ef..ab5a486 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml deleted file mode 100644 index 5ab55b9..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_3.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml deleted file mode 100644 index 1ad4a3f..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml new file mode 100644 index 0000000..c38b651 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_js_library_1_1_0_beta_18.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml deleted file mode 100644 index 6958289..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml deleted file mode 100644 index b2af125..0000000 --- a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_5_2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 4051f93..81ed488 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test.iml b/.idea/modules/kotlin-webgl-test.iml deleted file mode 100644 index 3bc84ee..0000000 --- a/.idea/modules/kotlin-webgl-test.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_main.iml b/.idea/modules/kotlin-webgl-test_main.iml deleted file mode 100644 index 61e01d4..0000000 --- a/.idea/modules/kotlin-webgl-test_main.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kotlin-webgl-test_test.iml b/.idea/modules/kotlin-webgl-test_test.iml deleted file mode 100644 index 6ac62a6..0000000 --- a/.idea/modules/kotlin-webgl-test_test.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/kudens.iml b/.idea/modules/kudens.iml new file mode 100644 index 0000000..bb33e4b --- /dev/null +++ b/.idea/modules/kudens.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_main.iml b/.idea/modules/kudens_main.iml new file mode 100644 index 0000000..70f44bd --- /dev/null +++ b/.idea/modules/kudens_main.iml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kudens_test.iml b/.idea/modules/kudens_test.iml new file mode 100644 index 0000000..4b8ee03 --- /dev/null +++ b/.idea/modules/kudens_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 77041ff..4562616 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'kotlin2js' apply plugin: 'idea' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +idea { + module { + name = "kudens" + } +} sourceSets { - main.kotlin.srcDirs += 'lib/kotludens' main.kotlin.srcDirs += 'src' } @@ -17,7 +24,7 @@ } buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0-beta-18' repositories { maven { url "http://nexus.astraeus.nl/nexus/content/groups/public" @@ -35,36 +42,63 @@ task jarSources(type:Jar){ from sourceSets.main.allSource + classifier = 'source' } + artifacts { compile jarSources } -def outDir = "${buildDir}/kotlin2js/main/" - compileKotlin2Js { kotlinOptions.metaInfo = true compileKotlin2Js.kotlinOptions.sourceMap = true - compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/generated/KotlinTest.js" + compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlinjs/kudens.js" compileKotlin2Js.kotlinOptions.suppressWarnings = true compileKotlin2Js.kotlinOptions.verbose = true } -jar { - from sourceSets.main.allSource - include "**/*.kt" +def outDir = "${buildDir}/kotlinjs/" +jar { from outDir include "**/*.js" manifest { attributes( - "Specification-Title": "Kotlin JavaScript Lib", - "Kotlin-JS-Module-Name": "test-library" + "Specification-Title": "Kudens game lib", + "Kotlin-JS-Module-Name": "lib-kudens" ) } } -jar.dependsOn(compileKotlin2Js) \ No newline at end of file +jar.dependsOn(compileKotlin2Js) + +build.doLast { + configurations.compile.each { File file -> + copy { + includeEmptyDirs = false + + from zipTree(file.absolutePath) + into "${projectDir}/web" + include { fileTreeElement -> + def path = fileTreeElement.path + path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/")) + } + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") { + authentication(userName: nexusUsername, password: nexusPassword) + } + snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0515d59..39bba2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Oct 20 20:26:54 CEST 2015 +#Sat Jan 21 15:59:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt index 0d4593c..a84a5a3 100644 --- a/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt +++ b/lib/kotludens/com/persesgames/map/tiled/TiledMap.kt @@ -194,14 +194,18 @@ } } - for (tileset in data.tilesets) { - if (Textures.has(tileset.name)) { - val tx = Textures.get(tileset.name) + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) - tx.render() + tx.render() + } } } + first = false } } diff --git a/lib/kotludens/com/persesgames/texture/Textures.kt b/lib/kotludens/com/persesgames/texture/Textures.kt index db24963..24840b6 100644 --- a/lib/kotludens/com/persesgames/texture/Textures.kt +++ b/lib/kotludens/com/persesgames/texture/Textures.kt @@ -81,16 +81,12 @@ val width: Int, val height: Int ) { - val shaderProgramMesh: ShaderProgramMesh + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) val left = -width / 2f val right = width / 2f val bottom = -height / 2f val top = height / 2f - init { - shaderProgramMesh = ShaderProgramMesh(shaderProgram) - } - fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) diff --git a/lib/kotludens/shooter/Shooter.kt b/lib/kotludens/shooter/Shooter.kt new file mode 100644 index 0000000..a02c911 --- /dev/null +++ b/lib/kotludens/shooter/Shooter.kt @@ -0,0 +1,205 @@ +package com.persesgames.shooter + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.game.Screen +import com.persesgames.input.EmptyInputProcessor +import com.persesgames.input.KeyCode +import com.persesgames.input.Keys +import com.persesgames.map.tiled.TiledMap +import com.persesgames.sound.Music +import com.persesgames.sound.Sounds +import com.persesgames.sprite.Sprite +import com.persesgames.sprite.SpriteBatch +import com.persesgames.text.Texts +import com.persesgames.texture.Textures +import org.w3c.dom.HTMLAudioElement +import org.w3c.dom.HTMLInputElement +import kotlin.browser.document + +/** + * Created by rnentjes on 19-4-16. + */ + +class GameInputProcessor : EmptyInputProcessor() { + + override fun keyPressed(charCode: Int) { + println("charCode: $charCode") + if (charCode == 32) { + Sounds.play("EXPLOSION", 0.5f) + } else if (charCode == 'x'.toInt()) { + Sounds.play("DROP", 0.75f) + } + } + + override fun pointerClick(pointer: Int, x: Float, y: Float) { + println("POINTER $pointer -> ($x, $y)") + } +} + +var music: HTMLAudioElement? = null +var showFPS: Boolean = true + +class WelcomeScreen : Screen() { + + override fun loadResources() { + println("loading resource!") + + //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) + + Textures.loadSpriteSheet("images/data-0.json") + + Keys.setInputProcessor(GameInputProcessor()) + } + + override fun update(time: Float, delta: Float) { } + + override fun render() { + + if (showFPS) { + Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") + } + } +} + +class GameScreen : Screen() { + val map = TiledMap("maps", "level_1_01.json") + + var sprites = SpriteBatch() + var x = 0f + var y = 15500f + var sprite = Sprite("SHIP") + var numberOfSprites: Int = 5000 + var time: Float = 0f + + override fun loadResources() { + Textures.load("SHIP", "images/ship2.png") + + Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) + Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) + + music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) + + Keys.setInputProcessor(GameInputProcessor()) + + println("width: ${map.data.width}") + println("height: ${map.data.height}") + println("layers: ${map.data.layers?.size}") + + val layers = map.data.layers + if (layers != null) { + println("layer0: ${layers[0].name}") + } + val tilesets = map.data.tilesets + if (tilesets != null) { + println("tilesets ${tilesets.size}") + println("tileset0: ${tilesets[0].name}") + } + } + + override fun update(time: Float, delta: Float) { + this.time = time + val speed = 1500f // units per second + + if (Keys.isDown(KeyCode.LEFT)) { + x -= delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.RIGHT)) { + x += delta * speed + println("x=$x") + } + + if (Keys.isDown(KeyCode.UP)) { + y += delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.DOWN)) { + y -= delta * speed + println("y=$y") + } + + if (Keys.isDown(KeyCode.MINUS)) { + if (numberOfSprites > 25) { + numberOfSprites = (numberOfSprites * 0.9f).toInt() + } + } + + if (Keys.isDown(KeyCode.PLUS)) { + numberOfSprites = (numberOfSprites * 1.1f).toInt() + } + } + + override fun render() { + var r = 0f + var d = 0f + var x = 0f + var y = 0f + + map.drawLayer(1, this.x, this.y) + map.drawLayer(2, this.x, this.y) + + val time = this.time / 10f + for (index in 0..numberOfSprites) { + r = index * 0.05f + d = index * 2.13f + x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() + y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() + + sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) + } + + sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) + + sprites.render() + + Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") + + if (showFPS) { + Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") + Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") + } + } +} + +fun main(args: Array) { + Game.view.setToHeight(1000f) + Game.view.drawMode = DrawMode.LINEAR + + Game.view.minAspectRatio = 0.9f + Game.view.maxAspectRatio = 1.5f + + Game.start(WelcomeScreen()) +} + +fun changeMusic(it: HTMLInputElement) { + val mus = music + + if (mus != null) { + if (it.checked) { + mus.volume = 0.5 + } else { + mus.volume = 0.0 + } + } +} + +fun showFPS(it: HTMLInputElement) { + showFPS = it.checked +} + +fun pause(it: HTMLInputElement) { + Game.pause = it.checked +} + +fun playGame() { + document.getElementById("menu")?.setAttribute("style", "display: none;") + + Game.setScreen(GameScreen()) +} + +fun fullscreen() { + Game.view.requestFullscreen() +} diff --git a/settings.gradle b/settings.gradle index 329f712..3181c92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'kotlin-webgl-test' +rootProject.name = 'kudens' diff --git a/src/com/persesgames/game/Game.kt b/src/com/persesgames/game/Game.kt new file mode 100644 index 0000000..3abdd56 --- /dev/null +++ b/src/com/persesgames/game/Game.kt @@ -0,0 +1,155 @@ +package com.persesgames.game + +import com.persesgames.texture.Textures +import org.khronos.webgl.WebGLRenderingContext +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement +import kotlin.browser.document +import kotlin.browser.window + +/** + * Created by rnentjes on 19-4-16. + */ + +enum class DrawMode { + LINEAR, + NEAREST +} + +class HTMLElements { + var container: HTMLElement + var webgl: WebGLRenderingContext + var canvas2d: CanvasRenderingContext2D + + init { + container = document.createElement("div") as HTMLElement + + val webGlCanvas = document.createElement("canvas") as HTMLCanvasElement + val canvas = document.createElement("canvas") as HTMLCanvasElement + + container.setAttribute("style", "position: absolute; left: 0px; top: 0px;") + webGlCanvas.setAttribute("style", "position: absolute; left: 0px; top: 0px;" ) + canvas.setAttribute("style", "position: absolute; left: 0px; top: 0px; z-index: 10; width: 1000px; height: 500px;" ) + + document.body!!.appendChild(container) + container.appendChild(webGlCanvas) + container.appendChild(canvas) + + webgl = webGlCanvas.getContext("webgl") as WebGLRenderingContext + canvas2d = canvas.getContext("2d") as CanvasRenderingContext2D + } +} + +object Game { + var started = false + val view: View = View() + val html: HTMLElements by lazy { HTMLElements() } + var currentScreen: Screen = DefaultScreen() + var start = Date().getTime() + var currentTime = start + var currentDelta = 0f + var pause: Boolean = false + + var fps = 0 + var fpsCount = 0 + var fpsCountTime = 0f + + fun gl() = html.webgl + + fun resize() { + val canvas = gl().canvas + + // Check if the canvas is not the same size. + val windowWidth = window.innerWidth.toInt() + val windowHeight = window.innerHeight.toInt() + + if (view.lastWindowWidth != windowWidth || + view.lastWindowHeight != windowHeight) { + view.lastWindowWidth = windowWidth + view.lastWindowHeight = windowHeight + view.windowWidth = windowWidth + view.windowHeight = windowHeight + + view.updateView() + + val textCanvas = html.canvas2d.canvas + + // Make the canvas the same size + canvas.width = view.width.toInt() + canvas.height = view.height.toInt() + + textCanvas.width = view.width.toInt() + textCanvas.height = view.height.toInt() + + gl().viewport(0, 0, view.width.toInt(), view.height.toInt()) + + val left = (windowWidth - view.windowWidth) / 2 + val top = (windowHeight - view.windowHeight) / 2 + + canvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 5; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + textCanvas.setAttribute("style", "position: absolute; left: ${left}px; top: ${top}px; z-index: 10; width: ${view.windowWidth}px; height: ${view.windowHeight}px;" ) + } + } + + fun start(startScreen: Screen) { + if (started) { + throw IllegalStateException("You can only start a game once!") + } + + setScreen(startScreen) + + // start game loop + started = true + gameLoop() + } + + fun setScreen(screen: Screen) { + currentScreen.closeResources() + + currentScreen = screen + + currentScreen.loadResources() + } + + fun gameLoop() { + if (!Textures.ready()) { + Game.gl().clearColor(1f, 1f, 1f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } else { + resize() + + if (!pause) { + html.canvas2d.clearRect(0.0, 0.0, view.width.toDouble(), view.height.toDouble()); + + Game.gl().clearColor(0f, 0f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + + Game.gl().enable(WebGLRenderingContext.BLEND); + Game.gl().blendFunc(WebGLRenderingContext.SRC_ALPHA, WebGLRenderingContext.ONE_MINUS_SRC_ALPHA); //ONE_MINUS_DST_ALPHA); + + val time = Date().getTime() + currentDelta = (time - currentTime) / 1000f + currentTime = time + + val timeInSeconds = (currentTime - start) / 1000f + + fpsCountTime += currentDelta + fpsCount++ + while (fpsCountTime > 1f) { + fps = fpsCount + fpsCountTime -= 1f + fpsCount = 0 + } + + currentScreen.update(timeInSeconds, currentDelta); + currentScreen.render() + } + } + + window.requestAnimationFrame { + gameLoop() + } + } + +} diff --git a/src/com/persesgames/game/Screen.kt b/src/com/persesgames/game/Screen.kt new file mode 100644 index 0000000..6fb558e --- /dev/null +++ b/src/com/persesgames/game/Screen.kt @@ -0,0 +1,35 @@ +package com.persesgames.game + +import org.khronos.webgl.WebGLRenderingContext + +/** + * Created by rnentjes on 19-4-16. + */ + +abstract class Screen { + + open fun loadResources() { + + } + + open fun closeResources() { + + } + + abstract fun update(time: Float, delta: Float) + + abstract fun render() + +} + + +class DefaultScreen: Screen() { + override fun update(time: Float, delta: Float) { + } + + override fun render() { + // show loading message? + Game.gl().clearColor(1f, 1f, 0f, 1f) + Game.gl().clear(WebGLRenderingContext.COLOR_BUFFER_BIT) + } +} \ No newline at end of file diff --git a/src/com/persesgames/game/View.kt b/src/com/persesgames/game/View.kt new file mode 100644 index 0000000..e5590e0 --- /dev/null +++ b/src/com/persesgames/game/View.kt @@ -0,0 +1,221 @@ +package com.persesgames.game + +import com.persesgames.math.Matrix4 + +enum class ViewType { + PROJECTION, + WIDTH, + HEIGHT, + ABSOLUTE +} + +class View( + var lastWindowWidth: Int = 2000, + var lastWindowHeight: Int = 1000, + var windowWidth: Int = 2000, + var windowHeight: Int = 1000, + var width: Float = 1024f, + var height: Float = 1024f, + var angle: Float = 60f, + var near: Float = -0.1f, + var far: Float = -100f, + var minAspectRatio: Float = 1f, + var maxAspectRatio: Float = 1f, + var leftOffset: Int = 0, + var bottomOffset: Int = 0, + var viewType: ViewType = ViewType.WIDTH, + var drawMode: DrawMode = DrawMode.LINEAR) { + var vMatrix = Matrix4() + var aspectRatio = 1f + + init { + updateView() + } + + fun requestFullscreen() { + println("Requesting fullscreen") + js(""" + if (document.webkitFullscreenElement) { + document.webkitCancelFullScreen(); + } else { + document.documentElement.webkitRequestFullScreen(); + } + """) + //if (document.fullscreenEnabled) { + // println("fullscreen Enabled") + //Game.html.container.requestFullscreen() + //document.documentElement?.requestFullscreen() + //} + } + + fun updateView() { + aspectRatio = windowWidth / windowHeight.toFloat() + + if (aspectRatio < minAspectRatio) { + aspectRatio = minAspectRatio + + windowHeight = (windowWidth / aspectRatio).toInt() + } + + if (aspectRatio > maxAspectRatio) { + aspectRatio = maxAspectRatio + + windowWidth = (windowHeight * aspectRatio).toInt() + } + + when (viewType) { + ViewType.ABSOLUTE -> { + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.WIDTH -> { + height = width / aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.HEIGHT -> { + width = height * aspectRatio + + vMatrix.setOrthographicProjection(0f, width, 0f, height, near, far) + } + ViewType.PROJECTION -> { + vMatrix.setPerspectiveProjection(angle, aspectRatio, near, far); + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + //println("width: $width, height: $height") + } + + fun screenToGameCoordX(screenX: Float): Float { + var result = screenX + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.HEIGHT -> { + result = (screenX / windowWidth * width) - width / 2 + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun screenToGameCoordY(screenY: Float): Float { + var result = screenY + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.HEIGHT -> { + result = -((screenY / windowHeight * height) - height / 2) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordX(gameX: Float): Float { + var result = gameX + val normalizedX = gameX + (width / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = (windowWidth / width * normalizedX) + } + ViewType.HEIGHT -> { + result = (windowWidth / width * normalizedX) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun gameToScreenCoordY(gameY: Float): Float { + var result = gameY + val normalizedY = gameY + (height / 2) + + when (viewType) { + ViewType.ABSOLUTE -> { + // nop + } + ViewType.WIDTH -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.HEIGHT -> { + result = windowHeight - (windowHeight / height * normalizedY) + } + ViewType.PROJECTION -> { + // uhm + } + else -> { + throw IllegalStateException("ViewType $viewType not implemented!") + } + } + + return result + } + + fun setToWidth(width: Float) { + this.width = width + this.viewType = ViewType.WIDTH + + updateView() + } + + fun setToHeight(height: Float) { + this.height = height + this.viewType = ViewType.HEIGHT + + updateView() + } + + fun setProjection(angle: Float) { + this.angle = angle + this.viewType = ViewType.PROJECTION + + updateView() + } + + fun setNear(near: Float) { + this.near = near + + updateView() + } + + fun setFar(far: Float) { + this.far = far + + updateView() + } +} \ No newline at end of file diff --git a/src/com/persesgames/input/Keys.kt b/src/com/persesgames/input/Keys.kt new file mode 100644 index 0000000..6705c67 --- /dev/null +++ b/src/com/persesgames/input/Keys.kt @@ -0,0 +1,141 @@ +package com.persesgames.input + +import com.persesgames.game.Game +import org.w3c.dom.events.Event +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.MouseEvent +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:18 + */ + +enum class KeyCode(val keyCode: Int) { + LEFT(37), + UP(38), + DOWN(40), + RIGHT(39), + SPACE(32), + MINUS(109), + PLUS(107), +} + +interface InputProcessor { + + fun keyPressed(charCode: Int) + + fun keyDown(keyCode: Int) + + fun keyUp(keyCode: Int) + + fun pointerClick(pointer: Int, x: Float, y: Float) + +} + +open class EmptyInputProcessor : InputProcessor { + override fun pointerClick(pointer: Int, x: Float, y: Float) { + } + + override fun keyDown(keyCode: Int) { + } + + override fun keyPressed(charCode: Int) { + } + + override fun keyUp(keyCode: Int) { + } + + +} + +object Keys { + + private val keys: MutableMap = HashMap(); + private var inputProcesser: InputProcessor = EmptyInputProcessor() + + init { + val body = document.body + if (body != null) { + body.on("keydown", true) { + Keys.keyDown(it) + } + + body.on("keyup", true) { + Keys.keyUp(it) + } + + body.on("keypress", true) { + Keys.keyPress(it) + } + + body.on("click", true) { + Keys.mouseClick(it) + } + + body.on("mousedown", true) { + Keys.mouseMove(it) + } + + body.on("mouseup", true) { + Keys.mouseMove(it) + } + + body.on("mousemove", true) { + Keys.mouseMove(it) + } + } + } + + fun setInputProcessor(processor: InputProcessor) { + this.inputProcesser = processor + } + + private fun keyDown(key: Event) { + if (key is KeyboardEvent) { + keys.put(key.keyCode, Date().getTime()) + + inputProcesser.keyDown(key.keyCode) + } + } + + private fun keyUp(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyUp(key.keyCode) + + keys.remove(key.keyCode) + } + } + + private fun keyPress(key: Event) { + if (key is KeyboardEvent) { + inputProcesser.keyPressed(key.charCode) + } + } + + private fun mouseClick(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + inputProcesser.pointerClick(event.button.toInt(), vx, vy) + } + } + + private fun mouseMove(event: Event) { + if (event is MouseEvent) { + val vx: Float = Game.view.screenToGameCoordX(event.clientX.toFloat()) + val vy: Float = Game.view.screenToGameCoordY(event.clientY.toFloat()) + + + } + } + + fun isDown(keyCode: Int) = keys.containsKey(keyCode) + + fun isDown(keyCode: KeyCode) = keys.containsKey(keyCode.keyCode) + +} diff --git a/src/com/persesgames/map/Map.kt b/src/com/persesgames/map/Map.kt new file mode 100644 index 0000000..b5d7bc8 --- /dev/null +++ b/src/com/persesgames/map/Map.kt @@ -0,0 +1,9 @@ +package com.persesgames.map + +/** + * Created by rnentjes on 22-7-16. + */ + +open class Map { + +} diff --git a/src/com/persesgames/map/tiled/TiledMap.kt b/src/com/persesgames/map/tiled/TiledMap.kt new file mode 100644 index 0000000..a84a5a3 --- /dev/null +++ b/src/com/persesgames/map/tiled/TiledMap.kt @@ -0,0 +1,211 @@ +package com.persesgames.map.tiled + +import com.persesgames.net.getUrlAsString +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures +import java.util.* +import kotlin.browser.window + +/** + * Created by rnentjes on 22-7-16. + */ + +class MapData { + var version: Int = 1 + var properties: MutableMap = HashMap() + var layers: Array? = null + var tilesets: Array? = null + + var height: Int = 0 + var width: Int = 0 + + var nextobjectid: Int = 0 + var orientation: String = "orthogonal" + var renderorder: String = "right-down" + var tileheight: Int = 0 + var tilewidth: Int = 0 +} + +class MapLayer { + var properties: MutableMap = HashMap() + + var data: Array? = null + var encoding: String = "" + var x: Int = 0 + var y: Int = 0 + var width: Int = 0 + var height: Int = 0 + var name: String = "" + var opacity: Float = 1f + var type: String = "" + var visible: Boolean = true + var draworder: String = "" +} + +class MapTileset { + var properties: MutableMap = HashMap() + + var firstgid: Int = 0 + var image: String = "" + var imageheight: Int = 0 + var imagewidth: Int = 0 + var margin: Int = 0 + var name: String = "" + var spacing: Int = 0 + var tilecount: Int = 0 + var tileheight: Int = 0 + var tilewidth: Int = 0 + var tileproperties: MutableMap> = HashMap() +} + +class TilesetIndex( + val texture: Texture?, + val tcLeft: Float, + val tcTop: Float, + val tcRight: Float, + val tcBottom: Float, + val scale: Float + ) { + constructor() : this(null, 0f, 0f, 0f, 0f, 0f) + + fun render(x: Float, y: Float) { + texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale) + } +} + +class TiledMap(dir: String = "", url: String) { + val properties: Map = HashMap() + val data: MapData + val tileset: Array + val tiles: Array + var first = true + //var tilesetIndex: Array = Array(0, { TilesetIndex() }) + + init { + var tileDir = dir + if (!tileDir.isEmpty() && !tileDir.endsWith("/")) { + tileDir = tileDir + "/" + } + + data = JSON.parse(getUrlAsString(tileDir + url)) + println("map data is loaded") + val tilesets = data.tilesets + if (tilesets != null) { + tileset = Array(tilesets.size, { "" }) + var maxGid = 0 + for (index in 0..tilesets.size - 1) { + tileset[index] = tilesets[index].name + Textures.load(tilesets[index].name, tileDir + tilesets[index].image) + maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount) + } + + tiles = Array(maxGid, { TilesetIndex() }) + } else { + tileset = Array(0, { "" }) + tiles = Array(0, { TilesetIndex() }) + } + + cacheTiles() + } + + fun cacheTiles() { + if (!Textures.ready()) { + window.setTimeout({ cacheTiles() }, 10) + } else { + val tilesets = data.tilesets + var tcLeft = 0f + var tcTop = 0f + var tcRight = 0f + var tcBottom = 0f + + if (tilesets != null) { + + + for (tileset in tilesets) { + val tilesHor = tileset.imagewidth / tileset.tilewidth + val tilesVer = tileset.imageheight / tileset.tileheight + val scale = (tileset.tilewidth / tileset.imagewidth.toFloat()) + + for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) { + val texture = Textures.get(tileset.name) + + val gid = index - tileset.firstgid + + val xi = gid % tilesHor + var yi = gid / tilesHor + yi = tilesVer - yi - 1 + val tw = 1f / tilesHor.toFloat() + val th = 1f / tilesVer.toFloat() + + val pixelW = 0.1f / tileset.tilewidth + val pixelH = 0.1f / tileset.tileheight + + tcLeft = xi * tw + tcRight = tcLeft + tw + + // switch up/down because of texture coord 0,0 in left bottom corner + tcBottom = yi * th + tcTop = tcBottom + th + + tcLeft += pixelW + tcRight -= pixelW + + tcBottom += pixelH + tcTop -= pixelH + + tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale) + } + } + } + } + } + + fun drawTile(tile: Int, x: Float, y: Float) { + tiles[tile].render(x, y) + } + + fun drawLayer(layerIndex: Int, xo: Float, yo: Float) { + var x = 0f + var y = 0f + val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)") + val layer = layers[layerIndex] + + val layerData = layer.data + if (layerData != null) { + for (index in layerData.indices) { + // todo: determine if in view + // todo: determine tilewidth + //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) { + drawTile(layerData[index], xo + x * 128f, yo + y * 128f) + + when (data.renderorder) { + "right-down" -> { + x++ + if (x >= layer.width) { + x = 0f + y-- + } + } + else -> { + throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this") + } + } + //} + } + } + + val tilesets = data.tilesets + if (tilesets != null) { + for (tileset in tilesets) { + if (Textures.has(tileset.name)) { + val tx = Textures.get(tileset.name) + + tx.render() + } + } + } + + + first = false + } +} diff --git a/src/com/persesgames/math/Matrix4.kt b/src/com/persesgames/math/Matrix4.kt new file mode 100644 index 0000000..3cb4ed4 --- /dev/null +++ b/src/com/persesgames/math/Matrix4.kt @@ -0,0 +1,192 @@ +package com.persesgames.math + +import org.khronos.webgl.Float32Array + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:43 + */ +class Matrix4 { + + internal var matrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + internal var temp = FloatArray(16) + + private val translateMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val scaleMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateXMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateYMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + private val rotateZMatrix = floatArrayOf(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f) + + fun get(): FloatArray { + return matrix + } + + fun getFloat32Array() = Float32Array(get().toTypedArray()) + + fun set(values: FloatArray) { + if (values.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + matrix = values + } + + fun setPerspectiveProjection(angle: Float, imageAspectRatio: Float, near: Float, far: Float) { + val r = (angle / 180f * Math.PI).toFloat() + val f = (1.0f / Math.tan((r / 2.0f).toDouble())).toFloat() + + matrix[0] = f / imageAspectRatio + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + + matrix[4] = 0.0f + matrix[5] = f + matrix[6] = 0.0f + matrix[7] = 0.0f + + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = -(far + near) / (far - near) + matrix[11] = -1.0f + + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = -(2.0f * far * near) / (far - near) + matrix[15] = 0.0f + } + + fun setOrthographicProjection(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { + matrix[0] = 2f / (right - left) + matrix[1] = 0f + matrix[2] = 0f + matrix[3] = 0f + + matrix[4] = 0f + matrix[5] = 2f / (top - bottom) + matrix[6] = 0f + matrix[7] = 0f + + matrix[8] = 0f + matrix[9] = 0f + matrix[10] = -2f / (far - near) + matrix[11] = 0f + + matrix[12] = - (right + left) / (right - left) + matrix[13] = - (top + bottom) / (top - bottom) + matrix[14] = - (far + near) / (far - near) + matrix[15] = 1f + } + + fun setToIdentity() { + matrix[0] = 1.0f + matrix[1] = 0.0f + matrix[2] = 0.0f + matrix[3] = 0.0f + matrix[4] = 0.0f + matrix[5] = 1.0f + matrix[6] = 0.0f + matrix[7] = 0.0f + matrix[8] = 0.0f + matrix[9] = 0.0f + matrix[10] = 1.0f + matrix[11] = 0.0f + matrix[12] = 0.0f + matrix[13] = 0.0f + matrix[14] = 0.0f + matrix[15] = 1.0f + } + + fun mul(other: Matrix4) { + mul(other.get()) + } + + protected fun mul(other: FloatArray) { + if (other.size != 16) { + throw IllegalArgumentException("Matrix size should be 16!") + } + + temp[0] = matrix[0] * other[0] + matrix[1] * other[4] + matrix[2] * other[8] + matrix[3] * other[12] + temp[1] = matrix[0] * other[1] + matrix[1] * other[5] + matrix[2] * other[9] + matrix[3] * other[13] + temp[2] = matrix[0] * other[2] + matrix[1] * other[6] + matrix[2] * other[10] + matrix[3] * other[14] + temp[3] = matrix[0] * other[3] + matrix[1] * other[7] + matrix[2] * other[11] + matrix[3] * other[15] + temp[4] = matrix[4] * other[0] + matrix[5] * other[4] + matrix[6] * other[8] + matrix[7] * other[12] + temp[5] = matrix[4] * other[1] + matrix[5] * other[5] + matrix[6] * other[9] + matrix[7] * other[13] + temp[6] = matrix[4] * other[2] + matrix[5] * other[6] + matrix[6] * other[10] + matrix[7] * other[14] + temp[7] = matrix[4] * other[3] + matrix[5] * other[7] + matrix[6] * other[11] + matrix[7] * other[15] + temp[8] = matrix[8] * other[0] + matrix[9] * other[4] + matrix[10] * other[8] + matrix[11] * other[12] + temp[9] = matrix[8] * other[1] + matrix[9] * other[5] + matrix[10] * other[9] + matrix[11] * other[13] + temp[10] = matrix[8] * other[2] + matrix[9] * other[6] + matrix[10] * other[10] + matrix[11] * other[14] + temp[11] = matrix[8] * other[3] + matrix[9] * other[7] + matrix[10] * other[11] + matrix[11] * other[15] + temp[12] = matrix[12] * other[0] + matrix[13] * other[4] + matrix[14] * other[8] + matrix[15] * other[12] + temp[13] = matrix[12] * other[1] + matrix[13] * other[5] + matrix[14] * other[9] + matrix[15] * other[13] + temp[14] = matrix[12] * other[2] + matrix[13] * other[6] + matrix[14] * other[10] + matrix[15] * other[14] + temp[15] = matrix[12] * other[3] + matrix[13] * other[7] + matrix[14] * other[11] + matrix[15] * other[15] + + matrix[0] = temp[0] + matrix[1] = temp[1] + matrix[2] = temp[2] + matrix[3] = temp[3] + matrix[4] = temp[4] + matrix[5] = temp[5] + matrix[6] = temp[6] + matrix[7] = temp[7] + matrix[8] = temp[8] + matrix[9] = temp[9] + matrix[10] = temp[10] + matrix[11] = temp[11] + matrix[12] = temp[12] + matrix[13] = temp[13] + matrix[14] = temp[14] + matrix[15] = temp[15] + } + + fun translate(x: Float, y: Float, z: Float) { + translateMatrix[12] = x + translateMatrix[13] = y + translateMatrix[14] = z + + mul(translateMatrix) + } + + fun scale(x: Float, y: Float, z: Float) { + scaleMatrix[0] = x + scaleMatrix[5] = y + scaleMatrix[10] = z + + mul(scaleMatrix) + } + + fun rotateX(angle: Float) { + rotateXMatrix[5] = Math.cos(angle.toDouble()).toFloat() + rotateXMatrix[6] = (-Math.sin(angle.toDouble())).toFloat() + rotateXMatrix[9] = Math.sin(angle.toDouble()).toFloat() + rotateXMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateXMatrix) + } + + fun rotateY(angle: Float) { + rotateYMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateYMatrix[2] = Math.sin(angle.toDouble()).toFloat() + rotateYMatrix[8] = (-Math.sin(angle.toDouble())).toFloat() + rotateYMatrix[10] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateYMatrix) + } + + fun rotateZ(angle: Float) { + rotateZMatrix[0] = Math.cos(angle.toDouble()).toFloat() + rotateZMatrix[1] = Math.sin(angle.toDouble()).toFloat() + rotateZMatrix[4] = (-Math.sin(angle.toDouble())).toFloat() + rotateZMatrix[5] = Math.cos(angle.toDouble()).toFloat() + + mul(rotateZMatrix) + } +} diff --git a/src/com/persesgames/net/NetUtils.kt b/src/com/persesgames/net/NetUtils.kt new file mode 100644 index 0000000..b59cbb3 --- /dev/null +++ b/src/com/persesgames/net/NetUtils.kt @@ -0,0 +1,18 @@ +package com.persesgames.net + +import org.w3c.xhr.XMLHttpRequest + +/** + * User: rnentjes + * Date: 30-7-16 + * Time: 16:39 + */ + +fun getUrlAsString(url: String): String { + val req = XMLHttpRequest() + + req.open("GET", url, false) + req.send(null) + + return req.responseText +} diff --git a/src/com/persesgames/shader/ShaderProgram.kt b/src/com/persesgames/shader/ShaderProgram.kt new file mode 100644 index 0000000..3c3aad5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgram.kt @@ -0,0 +1,110 @@ +package com.persesgames.shader + +import org.khronos.webgl.* + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:15 + */ + +class ShaderProgram( + val webgl: WebGLRenderingContext, + val drawType: Int, + vertexShaderSource: String, + fragmentShaderSource: String, + val vainfo: Array, + val setter: (program: ShaderProgram, data: T) -> Unit) { + + var shaderProgram: WebGLProgram + var vertex: WebGLShader + var fragment: WebGLShader + + var verticesBlockSize = 0 + var drawLength = 0 + + init { + vertex = compileShader(vertexShaderSource, WebGLRenderingContext.VERTEX_SHADER) + fragment = compileShader(fragmentShaderSource, WebGLRenderingContext.FRAGMENT_SHADER) + + shaderProgram = webgl.createProgram() ?: throw IllegalStateException("Unable to request shader program from webgl context!") + webgl.attachShader(shaderProgram, vertex) + webgl.attachShader(shaderProgram, fragment) + webgl.linkProgram(shaderProgram) + + if (webgl.getProgramParameter(shaderProgram, WebGLRenderingContext.LINK_STATUS) == false) { + println(webgl.getProgramInfoLog(shaderProgram)) + throw IllegalStateException("Unable to compile shader program!") + } + + webgl.useProgram(shaderProgram) + + this.verticesBlockSize = 0; + + // set attribute locations... + for (info in vainfo.iterator()) { + info.location = webgl.getAttribLocation(shaderProgram, info.locationName) + info.offset = verticesBlockSize; + + verticesBlockSize += info.numElements; + println("attrib: ${info.locationName}, info.location: ${info.location}, info.offset: ${info.offset}"); + } + + when(drawType) { + WebGLRenderingContext.TRIANGLES -> { + drawLength = verticesBlockSize * 3 + } + else -> { + drawLength = verticesBlockSize + } + } + + println("verticesBlockSize $verticesBlockSize"); + + println("ShaderProgram constructor done"); + } + + private fun compileShader(source: String, type: Int): WebGLShader { + val result: WebGLShader + + result = webgl.createShader(type) ?: throw IllegalStateException("Unable to request shader from webgl context!") + webgl.shaderSource(result, source) + webgl.compileShader(result) + + if (webgl.getShaderParameter(result, WebGLRenderingContext.COMPILE_STATUS) == false) { + throw IllegalStateException("Unable to compile shader!\n${source}\n\n${webgl.getShaderInfoLog(result)}") + } + + return result + } + + fun begin(attribBuffer: WebGLBuffer, userdata: T) { + webgl.useProgram(shaderProgram); + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + + // set attribute locations... + for (info in vainfo.iterator()) { + webgl.enableVertexAttribArray(info.location) + webgl.vertexAttribPointer(info.location, info.numElements, WebGLRenderingContext.FLOAT, false, verticesBlockSize * 4, info.offset * 4) + } + + setter(this, userdata) + } + + fun end() { + for (info in vainfo.iterator()) { + webgl.disableVertexAttribArray(info.location); + } + webgl.useProgram(null) + } + + fun getAttribLocation(location: String) = webgl.getAttribLocation(shaderProgram, location); + + fun getUniformLocation(location: String) = webgl.getUniformLocation(shaderProgram, location); + + fun setUniform1f(location: String, value: Float) { webgl.uniform1f(getUniformLocation(location), value); } + fun setUniform4f(location: String, v1: Float, v2: Float, v3: Float, v4: Float) { webgl.uniform4f(getUniformLocation(location), v1, v2, v3, v4); } + fun setUniform1i(location: String, value: Int) { webgl.uniform1i(getUniformLocation(location), value); } + fun setUniformMatrix4fv(location: String, value: Float32Array) { webgl.uniformMatrix4fv(getUniformLocation(location), false, value); } + +} diff --git a/src/com/persesgames/shader/ShaderProgramMesh.kt b/src/com/persesgames/shader/ShaderProgramMesh.kt new file mode 100644 index 0000000..f4c4dd5 --- /dev/null +++ b/src/com/persesgames/shader/ShaderProgramMesh.kt @@ -0,0 +1,71 @@ +package com.persesgames.shader + +import org.khronos.webgl.Float32Array +import org.khronos.webgl.WebGLBuffer +import org.khronos.webgl.WebGLRenderingContext + +/** + * User: rnentjes + * Date: 14-5-16 + * Time: 11:57 + */ + +class VertextAttributeInfo(val locationName: String, val numElements: Int) { + var location = 0 + var offset = 0 +} + +class ShaderProgramMesh( + val shaderProgram: ShaderProgram +) { + val webgl = shaderProgram.webgl + val data: Float32Array + var currentIndex: Int = 0 + val attribBuffer: WebGLBuffer + var counter = 0 + + init { + data = Float32Array(20000 - (20000 % shaderProgram.drawLength)) + + attribBuffer = webgl.createBuffer() ?: throw IllegalStateException("Unable to create webgl buffer!") + webgl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, attribBuffer); + } + + fun queue(vararg vertices: Float) { + queue(vertices as Array) + } + + fun queue(vertices: Array) { + data.set(vertices, currentIndex) + currentIndex += vertices.size + + if (currentIndex == data.length) { + println("Skipped draw call, to many values!") + currentIndex = 0 + } + } + + fun remaining() = data.length - currentIndex + + fun bufferFull() = currentIndex == data.length + + fun render(userdata: T) { + counter++ + if (currentIndex > 0) { +/* if (counter % 100 == 0) { + println("currentIndex=$currentIndex blockSize=${shaderProgram.verticesBlockSize} drawLength=${shaderProgram.drawLength} drawing=${(currentIndex / shaderProgram.verticesBlockSize).toInt()}") + }*/ + if (currentIndex % shaderProgram.verticesBlockSize != 0) { + throw IllegalStateException("Number of vertices not a multiple of the attribute block size!") + } + + shaderProgram.begin(attribBuffer, userdata) + + webgl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, data, WebGLRenderingContext.DYNAMIC_DRAW) + webgl.drawArrays(shaderProgram.drawType, 0, (currentIndex / shaderProgram.verticesBlockSize).toInt()) + currentIndex = 0 + + shaderProgram.end() + } + } +} diff --git a/src/com/persesgames/shooter/Shooter.kt b/src/com/persesgames/shooter/Shooter.kt deleted file mode 100644 index 864a1b6..0000000 --- a/src/com/persesgames/shooter/Shooter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.persesgames.shooter - -import com.persesgames.game.DrawMode -import com.persesgames.game.Game -import com.persesgames.game.Screen -import com.persesgames.input.EmptyInputProcessor -import com.persesgames.input.KeyCode -import com.persesgames.input.Keys -import com.persesgames.map.tiled.TiledMap -import com.persesgames.sound.Music -import com.persesgames.sound.Sounds -import com.persesgames.sprite.Sprite -import com.persesgames.sprite.SpriteBatch -import com.persesgames.text.Texts -import com.persesgames.texture.Textures -import org.w3c.dom.HTMLAudioElement -import org.w3c.dom.HTMLInputElement -import kotlin.browser.document - -/** - * Created by rnentjes on 19-4-16. - */ - -class GameInputProcessor : EmptyInputProcessor() { - - override fun keyPressed(charCode: Int) { - println("charCode: $charCode") - if (charCode == 32) { - Sounds.play("EXPLOSION", 0.5f) - } else if (charCode == 'x'.toInt()) { - Sounds.play("DROP", 0.75f) - } - } - - override fun pointerClick(pointer: Int, x: Float, y: Float) { - println("POINTER $pointer -> ($x, $y)") - } -} - -var music: HTMLAudioElement? = null -var showFPS: Boolean = true - -class WelcomeScreen : Screen() { - - override fun loadResources() { - println("loading resource!") - - //music = Music.play("music/DST-TechnoBasic.mp3", 1.0, looping = true) - - Textures.loadSpriteSheet("images/data-0.json") - - Keys.setInputProcessor(GameInputProcessor()) - } - - override fun update(time: Float, delta: Float) { } - - override fun render() { - - if (showFPS) { - Texts.drawText(20f, 100f, "Hello! FPS ${Game.fps}", font = "bold 72pt Arial") - } - } -} - -class GameScreen : Screen() { - val map = TiledMap("maps", "level_1_01.json") - - var sprites = SpriteBatch() - var x = 0f - var y = 15500f - var sprite = Sprite("SHIP") - var numberOfSprites: Int = 5000 - var time: Float = 0f - - override fun loadResources() { - Textures.load("SHIP", "images/ship2.png") - - Sounds.load("EXPLOSION", "sounds/Explosion7.ogg", channels = 2) - Sounds.load("DROP", "sounds/Bomb_Drop.ogg", channels = 4) - - music = Music.play("music/DST-TechnoBasic.mp3", 0.5, looping = true) - - Keys.setInputProcessor(GameInputProcessor()) - - println("width: ${map.data.width}") - println("height: ${map.data.height}") - println("layers: ${map.data.layers?.size}") - - val layers = map.data.layers - if (layers != null) { - println("layer0: ${layers[0].name}") - } - val tilesets = map.data.tilesets - if (tilesets != null) { - println("tilesets ${tilesets.size}") - println("tileset0: ${tilesets[0].name}") - } - } - - override fun update(time: Float, delta: Float) { - this.time = time - val speed = 1500f // units per second - - if (Keys.isDown(KeyCode.LEFT)) { - x -= delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.RIGHT)) { - x += delta * speed - println("x=$x") - } - - if (Keys.isDown(KeyCode.UP)) { - y += delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.DOWN)) { - y -= delta * speed - println("y=$y") - } - - if (Keys.isDown(KeyCode.MINUS)) { - if (numberOfSprites > 25) { - numberOfSprites = (numberOfSprites * 0.9f).toInt() - } - } - - if (Keys.isDown(KeyCode.PLUS)) { - numberOfSprites = (numberOfSprites * 1.1f).toInt() - } - } - - override fun render() { - var r = 0f - var d = 0f - var x = 0f - var y = 0f - - map.drawLayer(1, this.x, this.y) - map.drawLayer(2, this.x, this.y) - - val time = this.time / 10f - for (index in 0..numberOfSprites) { - r = index * 0.05f - d = index * 2.13f - x = 1000f + (Math.sin((time + d).toDouble()) * r).toFloat() - y = 500f + (Math.cos((time + d).toDouble()) * r).toFloat() - - sprites.draw(sprite, x.toFloat(), y.toFloat(), scale = 0.4f + Math.sin(time.toDouble() + r).toFloat(), rotation = r * 10f) - } - - sprites.draw(sprite, 350f, 350f, scale = 4f, rotation = -time) - - sprites.render() - - Texts.drawText(20f, 150f, "Drawing $numberOfSprites sprites per frame.") - - if (showFPS) { - Texts.drawText(20f, 100f, "FPS ${Game.fps}", font = "bold 72pt Arial", fillStyle = "red") - Texts.drawText(15f, -20f, "Music by DST", font = "bold 28pt Arial", fillStyle = "green") - } - } - -} - -fun main(args: Array) { - Game.view.setToHeight(1000f) - Game.view.drawMode = DrawMode.LINEAR - - Game.view.minAspectRatio = 0.9f - Game.view.maxAspectRatio = 1.5f - - Game.start(WelcomeScreen()) -} - -fun changeMusic(it: HTMLInputElement) { - val mus = music - - if (mus != null) { - if (it.checked) { - mus.volume = 0.5 - } else { - mus.volume = 0.0 - } - } -} - -fun showFPS(it: HTMLInputElement) { - showFPS = it.checked -} - -fun pause(it: HTMLInputElement) { - Game.pause = it.checked -} - -fun playGame() { - document.getElementById("menu")?.setAttribute("style", "display: none;") - - Game.setScreen(GameScreen()) -} - -fun fullscreen() { - Game.view.requestFullscreen() -} diff --git a/src/com/persesgames/sound/Music.kt b/src/com/persesgames/sound/Music.kt new file mode 100644 index 0000000..88a531d --- /dev/null +++ b/src/com/persesgames/sound/Music.kt @@ -0,0 +1,49 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document +import kotlin.dom.on + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 13:02 + */ + +object Music { + val playing: MutableSet = HashSet() + + fun load(url: String): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + + return audio; + } + + fun play(url: String, volume: Double = 0.75, looping: Boolean = false): HTMLAudioElement { + val audio = document.createElement("audio") as HTMLAudioElement + + audio.src = url + audio.volume = volume + audio.play() + + audio.on("ended", true, { + if (looping) { + audio.currentTime = 0.0 + audio.play() + } else { + println("REMOVING: $audio") + audio.remove() + playing.remove(audio) + } + }) + + return audio + } + + fun stopAll() { + + } +} diff --git a/src/com/persesgames/sound/Sounds.kt b/src/com/persesgames/sound/Sounds.kt new file mode 100644 index 0000000..8d8c549 --- /dev/null +++ b/src/com/persesgames/sound/Sounds.kt @@ -0,0 +1,62 @@ +package com.persesgames.sound + +import org.w3c.dom.HTMLAudioElement +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 18-5-16 + * Time: 12:34 + */ + +class Sound(val name:String, val url: String, val volume: Double = 0.75, val numberOfChannels: Int) { + var channels: Array + var nextChannel: Int = 0 + + init { + println("CREATING: $name") + channels = Array(numberOfChannels, { document.createElement("audio") as HTMLAudioElement }) + + for (audio in channels) { + audio.src = url + audio.pause() + audio.load() + audio.volume = volume + } + } + + fun play() { + println("PLAYING: $name - $nextChannel") + channels[nextChannel].currentTime = 0.0 + channels[nextChannel].play() + + nextChannel = (nextChannel + 1) % channels.size + } + + fun pause() { + for (audio in channels) { + audio.pause() + } + } +} + +object Sounds { + val sounds: MutableMap = HashMap() + + fun load(name: String, url: String, volume: Double = 0.75, channels: Int = 1 ) { + sounds.put(name, Sound(name, url, volume, channels)) + } + + fun play(name: String, volume: Float = 0.75f) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.play() + } + + fun pause(name: String) { + val sound: Sound = sounds[name] ?: throw IllegalArgumentException("Sound '$name' not found, load it first!") + + sound.pause() + } +} diff --git a/src/com/persesgames/sprite/SpriteBatch.kt b/src/com/persesgames/sprite/SpriteBatch.kt new file mode 100644 index 0000000..8f72f31 --- /dev/null +++ b/src/com/persesgames/sprite/SpriteBatch.kt @@ -0,0 +1,26 @@ +package com.persesgames.sprite + +import com.persesgames.texture.Texture +import com.persesgames.texture.Textures + +/** + * User: rnentjes + * Date: 20-4-16 + * Time: 13:48 + */ + +class Sprite(val textureName: String) { + val texture: Texture by lazy { Textures.get(textureName) } +} + +class SpriteBatch { + + fun draw(sprite: Sprite, x: Float, y: Float, scale: Float = 1f, rotation: Float = 0f) { + sprite.texture.queueDraw(x, y, scale, rotation) + } + + fun render() { + Textures.render() + } + +} diff --git a/src/com/persesgames/text/Texts.kt b/src/com/persesgames/text/Texts.kt new file mode 100644 index 0000000..b0e2a17 --- /dev/null +++ b/src/com/persesgames/text/Texts.kt @@ -0,0 +1,30 @@ +package com.persesgames.text + +import com.persesgames.game.Game + +/** + * Created by rnentjes on 16-5-16. + */ + +object Texts { + + fun drawText(x: Float, y: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + var yy = y + if (yy < 0) { + yy += Game.view.height + } + Game.html.canvas2d.fillStyle = fillStyle + Game.html.canvas2d.font = font + Game.html.canvas2d.fillText(message, x.toDouble(), yy.toDouble()) + } + + fun drawLeftTop(left: Float, top: Float, message: String, font: String = "bold 24pt Arial", fillStyle: String = "white") { + drawText( + Game.view.gameToScreenCoordX(-Game.view.width / 2f + left), + Game.view.gameToScreenCoordY(Game.view.height / 2f - top), + message, + font, + fillStyle + ) + } +} diff --git a/src/com/persesgames/texture/Sprites.kt b/src/com/persesgames/texture/Sprites.kt new file mode 100644 index 0000000..1b0fd0a --- /dev/null +++ b/src/com/persesgames/texture/Sprites.kt @@ -0,0 +1,7 @@ +package com.persesgames.texture + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 15:44 + */ diff --git a/src/com/persesgames/texture/Textures.kt b/src/com/persesgames/texture/Textures.kt new file mode 100644 index 0000000..24840b6 --- /dev/null +++ b/src/com/persesgames/texture/Textures.kt @@ -0,0 +1,246 @@ +package com.persesgames.texture + +import com.persesgames.game.DrawMode +import com.persesgames.game.Game +import com.persesgames.map.tiled.MapTileset +import com.persesgames.math.Matrix4 +import com.persesgames.shader.ShaderProgram +import com.persesgames.shader.ShaderProgramMesh +import com.persesgames.shader.VertextAttributeInfo +import org.khronos.webgl.WebGLRenderingContext +import org.khronos.webgl.WebGLTexture +import org.w3c.dom.HTMLImageElement +import org.w3c.fetch.Request +import java.util.* +import kotlin.browser.document + +/** + * User: rnentjes + * Date: 17-4-16 + * Time: 14:52 + */ + +private val vertexShaderSource = """ + attribute vec2 a_position; + attribute vec2 a_boundingBox; + attribute vec2 a_texCoord; + attribute float a_scale; + attribute float a_rotation; + + uniform mat4 u_projectionView; + + varying vec2 v_textCoord; + + mat4 scale(float scale) { + return mat4( + vec4(scale, 0.0, 0.0, 0.0), + vec4(0.0, scale, 0.0, 0.0), + vec4(0.0, 0.0, scale, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + } + + mat4 rotateZ(float angle) { + return mat4( + vec4(cos(angle), sin(angle), 0.0, 0.0), + vec4(-sin(angle), cos(angle), 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + } + + void main(void) { + v_textCoord = a_texCoord; + + vec4 scaledBox = vec4(a_boundingBox, 1.0, 1.0) * scale(a_scale) * rotateZ(a_rotation); + + gl_Position = u_projectionView * vec4(a_position + scaledBox.xy, 1.0, 1.0); + } +""" + +private val fragmentShaderSource = """ + precision mediump float; + + uniform sampler2D u_sampler; + + varying vec2 v_textCoord; + + void main(void) { + gl_FragColor = texture2D(u_sampler, v_textCoord); + } +""" + +class TextureData( + val vMatrix: Matrix4, + val texture: WebGLTexture +) + +class Texture( + val glTexture: WebGLTexture, + val shaderProgram: ShaderProgram, + val width: Int, + val height: Int +) { + val shaderProgramMesh: ShaderProgramMesh = ShaderProgramMesh(shaderProgram) + val left = -width / 2f + val right = width / 2f + val bottom = -height / 2f + val top = height / 2f + + fun queueDraw(x: Float, y: Float, scale: Float, rotation: Float) { + shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) + shaderProgramMesh.queue( x, y, left, top, 0f, 1f, scale, rotation) + shaderProgramMesh.queue( x, y, right, top, 1f, 1f, scale, rotation) + shaderProgramMesh.queue( x, y, right, top, 1f, 1f, scale, rotation) + shaderProgramMesh.queue( x, y, right, bottom, 1f, 0f, scale, rotation) + shaderProgramMesh.queue( x, y, left, bottom, 0f, 0f, scale, rotation) + + if (shaderProgramMesh.remaining() < 36) { + render() + } + } + + fun queueTileDraw(x: Float, y: Float, tcLeft: Float, tcTop: Float, tcRight: Float, tcBottom: Float, scale: Float) { + shaderProgramMesh.queue( x, y, left, bottom, tcLeft, tcBottom, scale, 0f) + shaderProgramMesh.queue( x, y, left, top, tcLeft, tcTop, scale, 0f) + shaderProgramMesh.queue( x, y, right, top, tcRight, tcTop, scale, 0f) + shaderProgramMesh.queue( x, y, right, top, tcRight, tcTop, scale, 0f) + shaderProgramMesh.queue( x, y, right, bottom, tcRight, tcBottom, scale, 0f) + shaderProgramMesh.queue( x, y, left, bottom, tcLeft, tcBottom, scale, 0f) + + if (shaderProgramMesh.remaining() < 36) { + render() + } + } + + fun render() { + Game.gl().activeTexture(WebGLRenderingContext.TEXTURE0) + Game.gl().bindTexture(WebGLRenderingContext.TEXTURE_2D, glTexture) + + shaderProgramMesh.render(TextureData(Game.view.vMatrix, glTexture)) + } +} + +/* +{ + "frame": {"x":921,"y":1,"w":182,"h":103}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":4,"w":182,"h":103}, + "sourceSize": {"w":190,"h":110}, + "pivot": {"x":0.5,"y":0.5} +}, +*/ + +class Rect(val x: Int, val y: Int, val w: Int, val h: Int) +class Size(val w: Int, val h: Int) +class Pivot(val x: Double, val y: Double) + +class SpriteSheetData( + val frame: Rect, + val rotated: Boolean, + val trimmed: Boolean, + val spriteSourceSize: Rect, + val sourceSize: Size, + val pivot: Pivot + ) + +class SpriteSheet( + val glTexture: WebGLTexture, + val shaderProgram: ShaderProgram, + val data: Map +) { + +} + +object Textures { + var textures = HashMap() + var startedLoading = 0 + var loaded = 0 + val shaderProgram: ShaderProgram + + init { + val setter = { program: ShaderProgram, data: TextureData -> + //program.webgl.activeTexture(WebGLRenderingContext.TEXTURE0); + //program.webgl.bindTexture(WebGLRenderingContext.TEXTURE_2D, data.texture); + + program.setUniform1i("u_sampler", 0) + program.setUniformMatrix4fv("u_projectionView", Game.view.vMatrix.getFloat32Array()) + } + + val vainfo = arrayOf( + VertextAttributeInfo("a_position", 2), + VertextAttributeInfo("a_boundingBox", 2), + VertextAttributeInfo("a_texCoord", 2), + VertextAttributeInfo("a_scale", 1), + VertextAttributeInfo("a_rotation", 1) + ) + + shaderProgram = ShaderProgram(Game.gl(), WebGLRenderingContext.TRIANGLES, vertexShaderSource, fragmentShaderSource, vainfo, setter) + } + + fun loadSpriteSheet(name: String) { + //val data = Request(name).json() + + //println(data) + } + + fun load(name: String, filename: String) { + val gl = Game.gl() + + startedLoading++ + + val webGlTexture = gl.createTexture() + if (webGlTexture != null) { + val image = document.createElement("img") as HTMLImageElement + image.onload = { + textureLoaded(webGlTexture, image) + val texture = Texture(webGlTexture, shaderProgram, image.width, image.height) + + textures.put(name, texture) + + loaded++ + println("loaded texture $loaded/$startedLoading ${ready()}") + } + image.src = filename + } else { + throw IllegalStateException("Couldn't create webgl texture!") + } + } + + fun load(mapTileSet: MapTileset) { + + } + + fun textureLoaded(texture: WebGLTexture, image: HTMLImageElement) { + val gl = Game.gl() + + gl.bindTexture(WebGLRenderingContext.TEXTURE_2D, texture) + gl.pixelStorei(WebGLRenderingContext.UNPACK_FLIP_Y_WEBGL, 1) // second argument must be an int + gl.texImage2D(WebGLRenderingContext.TEXTURE_2D, 0, WebGLRenderingContext.RGBA, WebGLRenderingContext.RGBA, WebGLRenderingContext.UNSIGNED_BYTE, image) + if (Game.view.drawMode == DrawMode.NEAREST) { + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MAG_FILTER, WebGLRenderingContext.NEAREST) + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MIN_FILTER, WebGLRenderingContext.NEAREST) + } else { + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MAG_FILTER, WebGLRenderingContext.LINEAR) + gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MIN_FILTER, WebGLRenderingContext.LINEAR) + } + gl.bindTexture(WebGLRenderingContext.TEXTURE_2D, null) + } + + fun ready() = loaded == startedLoading + + fun has(name: String) = textures[name] != null + fun get(name: String) = textures[name] ?: throw IllegalArgumentException("Texture with name $name is not loaded!") + + fun clear() { + // delete and unbind all textures... + } + + fun render() { + for ((key, value) in textures) { + value.render() + } + } + +} diff --git a/web/index.html b/web/index.html index c8615d7..91ced9c 100644 --- a/web/index.html +++ b/web/index.html @@ -51,13 +51,13 @@
- Pause
- Music
- Show FPS + Pause
+ Music
+ Show FPS