commit ade25af03a3b9d58c0558a290ba43b4bbbf8c9d1 Author: Vlad Rud Date: Fri Oct 4 21:16:58 2024 +0300 First day diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8eeddd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +sineus-final.exe diff --git a/assets/fonts/NorseBold_info.txt b/assets/fonts/NorseBold_info.txt new file mode 100644 index 0000000..c4a31de --- /dev/null +++ b/assets/fonts/NorseBold_info.txt @@ -0,0 +1,2 @@ +license: Freeware +link: https://www.fontspace.com/norse-font-f21080 \ No newline at end of file diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt new file mode 100644 index 0000000..83b541b --- /dev/null +++ b/assets/fonts/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2010, ParaType Ltd. (http://www.paratype.com/public), +with Reserved Font Names "PT Sans", "PT Serif" and "ParaType". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/PTSerif-Bold.ttf b/assets/fonts/PTSerif-Bold.ttf new file mode 100644 index 0000000..36d47eb Binary files /dev/null and b/assets/fonts/PTSerif-Bold.ttf differ diff --git a/assets/fonts/PTSerif-BoldItalic.ttf b/assets/fonts/PTSerif-BoldItalic.ttf new file mode 100644 index 0000000..fa30e55 Binary files /dev/null and b/assets/fonts/PTSerif-BoldItalic.ttf differ diff --git a/assets/fonts/PTSerif-Italic.ttf b/assets/fonts/PTSerif-Italic.ttf new file mode 100644 index 0000000..9b110a4 Binary files /dev/null and b/assets/fonts/PTSerif-Italic.ttf differ diff --git a/assets/fonts/PTSerif-Regular.ttf b/assets/fonts/PTSerif-Regular.ttf new file mode 100644 index 0000000..f87c0f1 Binary files /dev/null and b/assets/fonts/PTSerif-Regular.ttf differ diff --git a/assets/fonts/freefont_EULA_1-3.txt b/assets/fonts/freefont_EULA_1-3.txt new file mode 100644 index 0000000..97da423 --- /dev/null +++ b/assets/fonts/freefont_EULA_1-3.txt @@ -0,0 +1,49 @@ +JOEL CARROUCHE - FREE FONT LICENSE +version 1.3, january 2021 + + + +By downloading / installing this font you agree to the terms of this licence : + + +RIGHTS GRANTED + +This font is a freeware. You are free to use it to create graphics, logos, and artwork for personal or commercial projects. + +You may also embed the font file in pdf documents, applications, web pages or flash animations. + + + +RESTRICTIONS + +You may not modifiy the font files. + +You may not sell this font or licenses for this font. + +You may not redistribute or share this font without written permission of Jol Carrouch. +This means you cannot make the font available for download on your website without prior consent. + + + +COPYRIGHT + +Except for the right to use the font mentionned above, all other rights remain the sole property of Jol Carrouch. + + + + +NO WARRANTY + +The font files are provided as is, without warranty of any kind, either expressed or implied. The author shall not be liable for any damage resulting from the use of the font. + + + +CONTACT + +www.joelcarrouche.com + + +If you use this font for something cool, let me know ! + + + diff --git a/assets/fonts/norse.otf b/assets/fonts/norse.otf new file mode 100644 index 0000000..98ba001 Binary files /dev/null and b/assets/fonts/norse.otf differ diff --git a/assets/models/background.bbmodel b/assets/models/background.bbmodel new file mode 100644 index 0000000..69cd76c --- /dev/null +++ b/assets/models/background.bbmodel @@ -0,0 +1 @@ +{"meta":{"format_version":"4.10","model_format":"free","box_uv":false},"name":"background","model_identifier":"","visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"timeline_setups":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"plane","color":3,"origin":[0,0,0],"rotation":[0,0,0],"export":true,"visibility":true,"locked":false,"render_order":"default","allow_mirror_modeling":true,"vertices":{"vqd0":[8,5,8],"sszo":[8,0,-8],"puxc":[-8,5,8],"4RLd":[-8,0,-8],"XhyS":[8,2.3333387633540412,2.666684042732932],"5zpr":[-8,2.3333387633540412,2.666684042732932],"pp64":[8,0.6666748117065739,-2.666640602538963],"vwIL":[-8,0.6666748117065739,-2.666640602538963],"HWac":[8,-8.124035629837435,-12.992277764807229],"gOq5":[-8,-8.124035629837435,-12.992277764807229]},"faces":{"7V9fAqsd":{"uv":{"sszo":[16,1.125],"4RLd":[0,1.125],"pp64":[16,5.333359397461037],"vwIL":[0,5.333359397461037]},"vertices":["4RLd","pp64","vwIL","sszo"],"texture":0},"LZcwHZcF":{"uv":{"vqd0":[16,16],"puxc":[0,16],"XhyS":[16,10.666684042732932],"5zpr":[0,10.666684042732932]},"vertices":["vqd0","XhyS","5zpr","puxc"],"texture":0},"Aqhlu0jG":{"uv":{"XhyS":[16,10.666684042732932],"5zpr":[0,10.666684042732932],"pp64":[16,5.333359397461037],"vwIL":[0,5.333359397461037]},"vertices":["XhyS","pp64","vwIL","5zpr"],"texture":0},"iZSsukM9":{"uv":{"4RLd":[16,0],"sszo":[0,0],"HWac":[0,1],"gOq5":[16,1]},"vertices":["HWac","gOq5","sszo","4RLd"],"texture":0}},"type":"mesh","uuid":"e663511f-7947-9bcf-3696-da6ccdb6351c"},{"name":"plane","color":3,"origin":[-16,0,0],"rotation":[0,0,0],"export":true,"visibility":true,"locked":false,"render_order":"default","allow_mirror_modeling":true,"vertices":{"vqd0":[8,5,8],"sszo":[8,0,-8],"puxc":[-8,5,8],"4RLd":[-8,0,-8],"XhyS":[8,2.3333387633540412,2.666684042732932],"5zpr":[-8,2.3333387633540412,2.666684042732932],"pp64":[8,0.6666748117065739,-2.666640602538963],"vwIL":[-8,0.6666748117065739,-2.666640602538963],"HWac":[8,-8.124035629837435,-12.992277764807229],"gOq5":[-8,-8.124035629837435,-12.992277764807229]},"faces":{"7V9fAqsd":{"uv":{"4RLd":[0,1.125],"pp64":[16,5.333359397461037],"vwIL":[0,5.333359397461037],"sszo":[16,1.125]},"vertices":["4RLd","pp64","vwIL","sszo"],"texture":0},"LZcwHZcF":{"uv":{"vqd0":[16,16],"XhyS":[16,10.666684042732932],"5zpr":[0,10.666684042732932],"puxc":[0,16]},"vertices":["vqd0","XhyS","5zpr","puxc"],"texture":0},"Aqhlu0jG":{"uv":{"XhyS":[16,10.666684042732932],"pp64":[16,5.333359397461037],"vwIL":[0,5.333359397461037],"5zpr":[0,10.666684042732932]},"vertices":["XhyS","pp64","vwIL","5zpr"],"texture":0},"iZSsukM9":{"uv":{"HWac":[0,1],"gOq5":[16,1],"sszo":[0,0],"4RLd":[16,0]},"vertices":["HWac","gOq5","sszo","4RLd"],"texture":0}},"type":"mesh","uuid":"31f9187e-b7a5-8d0c-cae8-f04611625d83"},{"name":"plane","color":3,"origin":[16,0,0],"rotation":[0,0,0],"export":true,"visibility":true,"locked":false,"render_order":"default","allow_mirror_modeling":true,"vertices":{"vqd0":[8,5,8],"sszo":[8,0,-8],"puxc":[-8,5,8],"4RLd":[-8,0,-8],"XhyS":[8,2.3333387633540412,2.666684042732932],"5zpr":[-8,2.3333387633540412,2.666684042732932],"pp64":[8,0.6666748117065739,-2.666640602538963],"vwIL":[-8,0.6666748117065739,-2.666640602538963],"HWac":[8,-8.124035629837435,-12.992277764807229],"gOq5":[-8,-8.124035629837435,-12.992277764807229]},"faces":{"7V9fAqsd":{"uv":{"4RLd":[0,1.125],"pp64":[16,5.333359397461037],"vwIL":[0,5.333359397461037],"sszo":[16,1.125]},"vertices":["4RLd","pp64","vwIL","sszo"],"texture":0},"LZcwHZcF":{"uv":{"vqd0":[16,16],"XhyS":[16,10.666684042732932],"5zpr":[0,10.666684042732932],"puxc":[0,16]},"vertices":["vqd0","XhyS","5zpr","puxc"],"texture":0},"Aqhlu0jG":{"uv":{"XhyS":[16,10.666684042732932],"pp64":[16,5.333359397461037],"vwIL":[0,5.333359397461037],"5zpr":[0,10.666684042732932]},"vertices":["XhyS","pp64","vwIL","5zpr"],"texture":0},"iZSsukM9":{"uv":{"HWac":[0,1],"gOq5":[16,1],"sszo":[0,0],"4RLd":[16,0]},"vertices":["HWac","gOq5","sszo","4RLd"],"texture":0}},"type":"mesh","uuid":"eed92c19-a9c4-1afa-2731-bfe15cc13ece"}],"outliner":["e663511f-7947-9bcf-3696-da6ccdb6351c","31f9187e-b7a5-8d0c-cae8-f04611625d83","eed92c19-a9c4-1afa-2731-bfe15cc13ece"],"textures":[{"path":"C:\\Dev\\sineus-final\\assets\\models\\background.png","name":"background.png","folder":"block","namespace":"","id":"0","group":"","width":128,"height":128,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"3faa339c-1a1f-acb0-d167-578e9ad5cc6b","relative_path":"background.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAA+lJREFUeF7tnEFu01AUReMZW6gQjBkyZcYGkFgOw26EPbAFdsUMlJZKURS3/j7/J7nvn4597ffuOXbcVOry8dunvwd/pm1gUYBp2T8trgBz81eAyfkrgAL4Eji1A74DTI3fl8DJ8R8Oy8PjZ78HmFgDBZgY/tP3AD4B5jZAAebm7xNgcv4KoAD+FjC1A74DTI3f3wImx68ACvDw4/HyN4Hvfs1Tzp/vbbumd3Oy77IqwGkl6Quf420FvqZHYi9nu28T4LX7I6mEXuBf+hix+/mMrddo3JELMLKMtgfz+tGNpTRfdjCk5nkaAv0EuNePjNHwt94A15qjAf7zH4PWXgIbT3Tx8NY7o8c1X85xq8KPO9/q2jv6GyvAcaBrSxBU/g5e3SPPApxDGlHiNUQYMXf3yu/rhNu/Cu5V7ggRes0W/nnepNZ/DtsFOD17z8KpEL1maZ2j13VbqPV6vzjZdZ8AI1+03gLRu/i3rvcaoN6zXLrW2nx7r312PibAqKdCy13RciyBvXadvSDWzrdnxq0zXDh3PwFGPhVaILfcRfS8e26APYC3zrkmwivX7C/AcditRm5dbO9xI8veO9Od5cYIcMungdCbFBsrwJ5HZMv4wm5p6+Kx1xOAfjQIG8O+dILrCjBkBU9KGlAA0l6BrAIUgEhWUADSXoGsAhSASFZQANJegawCFIBIVlg+/PzifwghDYZnFSAcIB1fAWiD4XkFCAdIx1cA2mB4XgHCAdLxFYA2GJ5XgHCAdHwFoA2G5xUgHCAdXwFog+F5BQgHSMdXANpgeF4BwgHS8RWANhieV4BwgHR8BaANhucVIBwgHV8BaIPheQUIB0jHVwDaYHheAcIB0vEVgDYYnleAcIB0fAWgDYbnFSAcIB1fAWiD4XkFCAdIx1cA2mB4XgHCAdLxFYA2GJ5XgHCAdHwFoA2G5xUgHCAdXwFog+F5BQgHSMdXANpgeF4BwgHS8RWANhieV4BwgHR8BaANhucVIBwgHV8BaIPh+eX976/+q9hwiGR8BSDtFcgqQAGIZAUFIO0VyCpAAYhkBQUg7RXIKkABiGQFBSDtFcgqQAGIZAUFIO0VyCpAAYhkBQUg7RXIKkABiGQFBSDtFcgqQAGIZAUFIO0VyCpAAYhkBQUg7RXIKkABiGQFBSDtFcgqQAGIZAUFIO0VyCpAAYhkBQUg7RXIKkABiGQFBSDtFcgqQAGIZAUFIO0VyCpAAYhkBQUg7RXIKkABiGQFBSDtFcgqQAGIZAUFIO0VyCpAAYhkBQUg7RXIKkABiGQFBSDtFcgqQAGIZAUFIO0VyCpAAYhkBQUg7RXIKkABiGSFf7kXRB/+oPVcAAAAAElFTkSuQmCC"}]} \ No newline at end of file diff --git a/assets/models/background.mtl b/assets/models/background.mtl new file mode 100644 index 0000000..97e19a4 --- /dev/null +++ b/assets/models/background.mtl @@ -0,0 +1,4 @@ +# Made in Blockbench 4.11.1 +newmtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b +map_Kd background.png +newmtl none \ No newline at end of file diff --git a/assets/models/background.obj b/assets/models/background.obj new file mode 100644 index 0000000..1be514f --- /dev/null +++ b/assets/models/background.obj @@ -0,0 +1,111 @@ +# Made in Blockbench 4.11.1 +mtllib background.mtl + +o plane +v 0.5 0.3125 0.5 +v 0.5 0 -0.5 +v -0.5 0.3125 0.5 +v -0.5 0 -0.5 +v 0.5 0.14583367270962758 0.16666775267080824 +v -0.5 0.14583367270962758 0.16666775267080824 +v 0.5 0.04166717573166087 -0.16666503765868518 +v -0.5 0.04166717573166087 -0.16666503765868518 +v 0.5 -0.5077522268648397 -0.8120173603004518 +v -0.5 -0.5077522268648397 -0.8120173603004518 +vt 0 0.9296875 +vt 0 0.6666650376586851 +vt 1 0.6666650376586851 +vt 1 0.9296875 +vt 1 0 +vt 1 0.33333224732919176 +vt 0 0.33333224732919176 +vt 0 0 +vt 1 0.33333224732919176 +vt 1 0.6666650376586851 +vt 0 0.6666650376586851 +vt 0 0.33333224732919176 +vt 0 1 +vt 0 0.9375 +vt 1 0.9375 +vt 1 1 +vn 0 0.9922777648072294 -0.12403562983743448 +vn 0 0.8944269724450329 -0.447214032609457 +vn 0 0.9544799780350297 -0.2982749931359468 +vn 0 0.5235550571328688 -0.8519918439460548 +usemtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b +f 4/1/1 8/2/1 7/3/1 2/4/1 +f 1/5/2 5/6/2 6/7/2 3/8/2 +f 5/9/3 7/10/3 8/11/3 6/12/3 +f 2/13/4 9/14/4 10/15/4 4/16/4 +o plane +v -0.5 0.3125 0.5 +v -0.5 0 -0.5 +v -1.5 0.3125 0.5 +v -1.5 0 -0.5 +v -0.5 0.14583367270962758 0.16666775267080824 +v -1.5 0.14583367270962758 0.16666775267080824 +v -0.5 0.04166717573166087 -0.16666503765868518 +v -1.5 0.04166717573166087 -0.16666503765868518 +v -0.5 -0.5077522268648397 -0.8120173603004518 +v -1.5 -0.5077522268648397 -0.8120173603004518 +vt 0 0.9296875 +vt 0 0.6666650376586851 +vt 1 0.6666650376586851 +vt 1 0.9296875 +vt 1 0 +vt 1 0.33333224732919176 +vt 0 0.33333224732919176 +vt 0 0 +vt 1 0.33333224732919176 +vt 1 0.6666650376586851 +vt 0 0.6666650376586851 +vt 0 0.33333224732919176 +vt 0 1 +vt 0 0.9375 +vt 1 0.9375 +vt 1 1 +vn 0 0.9922777648072294 -0.12403562983743448 +vn 0 0.8944269724450329 -0.447214032609457 +vn 0 0.9544799780350297 -0.2982749931359468 +vn 0 0.5235550571328688 -0.8519918439460548 +usemtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b +f 14/17/5 18/18/5 17/19/5 12/20/5 +f 11/21/6 15/22/6 16/23/6 13/24/6 +f 15/25/7 17/26/7 18/27/7 16/28/7 +f 12/29/8 19/30/8 20/31/8 14/32/8 +o plane +v 1.5 0.3125 0.5 +v 1.5 0 -0.5 +v 0.5 0.3125 0.5 +v 0.5 0 -0.5 +v 1.5 0.14583367270962758 0.16666775267080824 +v 0.5 0.14583367270962758 0.16666775267080824 +v 1.5 0.04166717573166087 -0.16666503765868518 +v 0.5 0.04166717573166087 -0.16666503765868518 +v 1.5 -0.5077522268648397 -0.8120173603004518 +v 0.5 -0.5077522268648397 -0.8120173603004518 +vt 0 0.9296875 +vt 0 0.6666650376586851 +vt 1 0.6666650376586851 +vt 1 0.9296875 +vt 1 0 +vt 1 0.33333224732919176 +vt 0 0.33333224732919176 +vt 0 0 +vt 1 0.33333224732919176 +vt 1 0.6666650376586851 +vt 0 0.6666650376586851 +vt 0 0.33333224732919176 +vt 0 1 +vt 0 0.9375 +vt 1 0.9375 +vt 1 1 +vn 0 0.9922777648072294 -0.12403562983743448 +vn 0 0.8944269724450329 -0.447214032609457 +vn 0 0.9544799780350297 -0.2982749931359468 +vn 0 0.5235550571328688 -0.8519918439460548 +usemtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b +f 24/33/9 28/34/9 27/35/9 22/36/9 +f 21/37/10 25/38/10 26/39/10 23/40/10 +f 25/41/11 27/42/11 28/43/11 26/44/11 +f 22/45/12 29/46/12 30/47/12 24/48/12 \ No newline at end of file diff --git a/assets/models/background.png b/assets/models/background.png new file mode 100644 index 0000000..8af941e Binary files /dev/null and b/assets/models/background.png differ diff --git a/game.odin b/game.odin new file mode 100644 index 0000000..099965b --- /dev/null +++ b/game.odin @@ -0,0 +1,144 @@ +package main + +import rl "vendor:raylib" +import "vendor:raylib/rlgl" +import "core:fmt" +import "core:math" +import "core:math/ease" +import "core:math/rand" +import "core:strings" +import "core:strconv" + +// Virtual game field dimensions +GameField := vec2{400, 200} + +Game :: struct { + using state: GameState, + health: u8, + player: Player, + camera: rl.Camera3D, + camera_offset: vec3, + score: u32, + background: rl.Texture, + plane: rl.Model, + bullets: [dynamic]Bullet, + snake_max_health: int, + snake_health: int +} + + + +game_init :: proc(prev: ^GameState = nil) -> ^GameState { + state := new(Game) + state.previous = prev + state.variant = state + state.draw = game_draw + state.update = game_update + state.free = game_free + rlgl.DisableBackfaceCulling() + + img := rl.GenImageChecked(1024, 1024, 128, 128, rl.Color{60, 255, 255, 255}, rl.Color{30, 220, 220, 255}) + state.background = rl.LoadTextureFromImage(img) + + state.plane = rl.LoadModel(".\\assets\\models\\background.obj") + rl.UnloadImage(img) + + game_setup(state) + return state +} + +game_setup :: proc(game: ^Game) { + clear(&game.bullets) + game.player = player_spawn({GameField.x / 2 + 50, 20, 0}) + game.health = 100 + + snake_spawn({10, 10, 0}, math.PI, 70) + + + for segment in Segments { + game.snake_max_health += int(segment.health) + } + + game.camera = rl.Camera3D{ + target = game.player.pos, + position = game.player.pos + vec3backward * 50, + fovy = 60, + //offset = WSize/2, + projection = rl.CameraProjection.PERSPECTIVE, + up = vec3up + } + game.camera.target.x = clamp(game.camera.target.x, -GameField.x/2, GameField.x/2) + game.camera.target.y = clamp(game.camera.target.y, 0, GameField.y) + game.camera.position = game.camera.target + vec3backward * 50 +} + + +game_gen_level :: proc(game: ^Game) { + +} + +game_update :: proc(state: ^GameState, delta: f32) { + game := transmute(^Game)state + using game + + + player_update(&player, game, delta) + #reverse for &bullet, i in bullets { + bullet_process(&bullet, game, delta) + if !bullet.alive { + unordered_remove(&bullets, i) + } + } + snake_process(game, delta) + target_offset := player.vel / 5 + camera_offset = rl.Vector3MoveTowards(camera_offset, target_offset, rl.Vector3Length(target_offset - camera_offset) * 10 * delta) + camera.target = player.pos + camera_offset + camera.target.x = clamp(camera.target.x, -GameField.x/2, GameField.x/2) + camera.target.y = clamp(camera.target.y, 0, GameField.y) + camera.position = camera.target + vec3backward * 50 + +} + +game_draw :: proc(state: ^GameState) { + + game := transmute(^Game)state + using game + + rl.BeginMode3D(camera) + rl.DrawModel(game.plane, {0, 0, 500}, 1000, rl.WHITE) + + yy : i32 = 0 + + snake_draw(game) + player_draw(&player) + for bullet in bullets { + bullet_draw(bullet) + } + rl.EndMode3D() + rl.DrawText(rl.TextFormat("HEALTH: %d", snake_health), 0, 0, 20, rl.BLACK) + rl.DrawText(rl.TextFormat("STATE: %s", Head.state), 0, 20, 20, rl.BLACK) + + + hb_text : cstring = "Jörmungandr" + height := 30 * (WSize.y / 480) + hb_health : f32 = f32(snake_health) / f32(snake_max_health) + if snake_health == 0 { + hb_health = f32(Head.health) / f32(Head.max_health) + hb_text = "Jörmungandr's head" + } + rl.DrawRectangleV({0, WSize.y - height - 10}, {WSize.x, height + 10}, rl.WHITE) + hb_width := hb_health * WSize.x + rl.DrawRectangleV({WSize.x / 2 - hb_width / 2, WSize.y - height - 7}, {hb_width, height + 4}, rl.RED) + draw_text_centered(FontTitle, hb_text, {WSize.x / 2, WSize.y - height / 2}, height) +} + + + +game_free :: proc(state: ^GameState) { + game := transmute(^Game)state + + rl.UnloadTexture(game.background) + free(state) +} + + diff --git a/main.odin b/main.odin new file mode 100644 index 0000000..1f5c191 --- /dev/null +++ b/main.odin @@ -0,0 +1,55 @@ +package main + +import rl "vendor:raylib" + +vec3 :: [3]f32 +vec2 :: [2]f32 +vec3right := vec3{1, 0, 0} +vec3left := vec3{-1, 0, 0} +vec3up := vec3{0, 1, 0} +vec3down := vec3{0, -1, 0} +vec3forward := vec3{0, 0, 1} +vec3backward := vec3{0, 0, -1} + + +WSize := [2]f32{} +WSizei := [2]i32{} + +WindowShouldExit := false + +FontUI: rl.Font +FontTitle: rl.Font + +main :: proc() { + rl.SetConfigFlags(rl.ConfigFlags{.MSAA_4X_HINT, .WINDOW_MAXIMIZED, .WINDOW_RESIZABLE}) + rl.InitWindow(800, 480, "Ragnarøkkr") + WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()} + WSize = {f32(WSizei.x), f32(WSizei.y)} + + FontUI = rl.LoadFontEx(".\\assets\\fonts\\PTSerif-Regular.ttf", 96, nil, 2048) + FontTitle = rl.LoadFontEx(".\\assets\\fonts\\norse.otf", 96*2, nil, 2048) + + + game := game_init() + stack_push(game) + menu := menu_init(game) + stack_push(menu) + + for !WindowShouldExit { + if rl.IsWindowResized() { + WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()} + WSize = {f32(WSizei.x), f32(WSizei.y)} + } + + state := stack_top() + delta := rl.GetFrameTime() + timers_process(delta) + tweens_process(delta) + state->update(delta) + + rl.BeginDrawing() + rl.ClearBackground(rl.SKYBLUE) + state->draw() + rl.EndDrawing() + } +} \ No newline at end of file diff --git a/menu.odin b/menu.odin new file mode 100644 index 0000000..ff931c4 --- /dev/null +++ b/menu.odin @@ -0,0 +1,83 @@ +package main + +import rl "vendor:raylib" +import "core:math/ease" + + +Menu_Buttons :: enum { + START, + HOW_TO_PLAY, + EXIT +} + +menu_strings := [Menu_Buttons]cstring { + .START = "Старт", + .HOW_TO_PLAY = "Как играть?", + .EXIT = "Выход" +} + +Menu :: struct { + using state: GameState, + + list: MenuList(Menu_Buttons), + +} + +menu_init :: proc(prev: ^GameState = nil) -> ^GameState { + state := new(Menu) + state.variant = state + state.list = MenuList(Menu_Buttons){ + state = state, + position = {100, WSize.y / 2}, + line_size = 60, + font_size = 48, + elements = &menu_strings, + menu_pressed = menu_button_pressed, + background = rl.Color{50, 10, 110, 10} + } + state.update = menu_update + state.draw = menu_draw + state.free = menu_free + state.previous = prev + + return state +} + +menu_update :: proc(state: ^GameState, delta: f32) { + menu := transmute(^Menu)state + menu.list.position.y = WSize.y / 2 + + menu_list_update(&menu.list) +} + +menu_button_pressed :: proc(state: ^GameState, el: Menu_Buttons) { + switch el { + case .START: + stack_pop() + case .HOW_TO_PLAY: + // howtoplay := howtoplay_init(state) + // stack_push(howtoplay) + case .EXIT: + WindowShouldExit = true + return + } +} + +menu_draw :: proc(state: ^GameState) { + menu := transmute(^Menu)state + + menu.previous.draw(menu.previous) + + TitleFontSize :: 96 + TitleSpacing :: 3 + TitleText :: "Ragnarøkkr" + TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing) + rl.DrawTextPro(FontTitle, TitleText, {WSize.x - 50, 50}, {TitleSize.x, 0}, 0, 96, 3, rl.WHITE) + + menu_list_draw(&menu.list) + +} + +menu_free :: proc(state: ^GameState) { + free(state) +} diff --git a/menu_list.odin b/menu_list.odin new file mode 100644 index 0000000..e1340e2 --- /dev/null +++ b/menu_list.odin @@ -0,0 +1,98 @@ +package main + +import rl "vendor:raylib" +import "core:math/ease" +import "core:fmt" + +MenuList :: struct($T: typeid) { + state: ^GameState, + position: vec2, + line_size: f32, + font_size: f32, + active_element: T, + active_marker: vec2, + tween: ^Tween, + elements: ^[T]cstring, + menu_pressed: proc(state: ^GameState, element: T), + background: rl.Color, + mouse_pos: vec2, +} + + +menu_list_update :: proc(list: ^MenuList($T)) { + + activate_element := false + prev_element := cast(i8)list.active_element + cur_element := prev_element + + last_mouse_pos := list.mouse_pos + list.mouse_pos = rl.GetMousePosition() + + size := menu_list_get_size(list) + if rl.CheckCollisionPointRec(list.mouse_pos, rl.Rectangle{ + x = list.position.x, + y = list.position.y, + width = size.x, + height = size.y, + }) { + if last_mouse_pos != list.mouse_pos { + mouse_relative := list.mouse_pos - list.position + cur_element = i8(mouse_relative.y / list.line_size) + } + if rl.IsMouseButtonPressed(rl.MouseButton.LEFT) { + list.menu_pressed(list.state, list.active_element) + } + } + last_mouse_pos = list.mouse_pos + + + + if rl.IsKeyPressed(rl.KeyboardKey.DOWN) { + cur_element += 1 + } + if rl.IsKeyPressed(rl.KeyboardKey.UP) { + cur_element -= 1 + } + if prev_element != cur_element { + if cur_element < 0 { cur_element = len(T) -1 } + if cur_element == len(T) { cur_element = 0 } + list.active_element = cast(T)cur_element + if list.tween != nil { + tween_cancel(list.tween) + } + list.tween = tween_to( + &list.active_marker.y, + f32(list.active_element) * list.line_size, + 0.25, + ease.Ease.Quadratic_Out, + ) + } + if rl.IsKeyPressed(rl.KeyboardKey.ENTER) || rl.IsKeyPressed(rl.KeyboardKey.SPACE) { + list.menu_pressed(list.state, list.active_element) + } +} + +menu_list_draw :: proc(list: ^MenuList($T)) { + if list.background[3] != 0 { + size := menu_list_get_size(list) + rl.DrawRectangleV(list.position - {40, 40}, size + {80, 80}, list.background) + } + rl.DrawTextEx(FontUI, ">", list.position + list.active_marker + {-30, 0}, 48, 2, rl.WHITE) + for el, i in list.elements { + pos := list.position + {0, f32(i) * list.line_size} + rl.DrawTextEx(FontUI, el, pos, list.font_size, 2, rl.WHITE) + } +} + + +menu_list_get_size :: proc(list: ^MenuList($T)) -> vec2 { + size := vec2{} + for el, i in list.elements { + line_size := rl.MeasureTextEx(FontUI, el, list.font_size, 2) + if line_size.x > size.x { + size.x = line_size.x + } + size.y += list.line_size + } + return size +} diff --git a/ols.json b/ols.json new file mode 100644 index 0000000..a20136c --- /dev/null +++ b/ols.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json", + "enable_document_symbols": true, + "enable_hover": true, + "enable_snippets": true, + "enable_format": true +} \ No newline at end of file diff --git a/player.odin b/player.odin new file mode 100644 index 0000000..c52591b --- /dev/null +++ b/player.odin @@ -0,0 +1,134 @@ +package main + +import rl "vendor:raylib" +import "core:math" +import "core:fmt" + + +Player :: struct { + pos: vec3, + vel: vec3, + dir: f32, + radius: f32, + thrust: f32, + max_speed: f32, + is_dodging: bool, + can_dodge: bool, + can_shoot: bool, + is_invulnerable: bool, + is_dead: bool +} + +player_spawn :: proc(position: vec3) -> Player { + return Player{ + pos = position, + radius = 1, + max_speed = 40, + dir = 0, + vel = {-40, 0, 0}, + can_dodge = true, + can_shoot = true + } +} + +player_update :: proc(player: ^Player, game: ^Game, delta: f32) { + using player + + pos += vel * delta + + mouse_ray := rl.GetMouseRay(rl.GetMousePosition(), game.camera) + mouse_pos : vec3 + hit := rl.GetRayCollisionQuad(mouse_ray, + {-1000, -1000, 0}, + {-1000, 1000, 0}, + {1000, 1000, 0}, + {1000, -1000, 0} + ) + if hit.hit { + mouse_pos = hit.point + } + + mouse_diff := mouse_pos - pos + mouse_angle := math.atan2(-mouse_diff.y, mouse_diff.x) + + if !is_dead { + if !is_dodging { + dir = angle_rotate(dir, mouse_angle, math.PI * 2 * delta) + dir_vector := get_vec_from_angle(dir) + + thrust = 0 + if rl.IsKeyDown(rl.KeyboardKey.W) { + thrust = 70 + } + if thrust > 0 { + vel = rl.Vector3MoveTowards(vel, dir_vector * max_speed, thrust * delta) + } + } + if thrust == 0 { + vel = rl.Vector3MoveTowards(vel, {0, -30, 0}, 20 * delta) + } + + if rl.IsMouseButtonPressed(rl.MouseButton.RIGHT) && can_dodge { + is_dodging = true + can_dodge = false + timer_start(0.45, player, proc(data: rawptr) { + player := transmute(^Player)data + player.is_dodging = false + }) + timer_start(0.55, player, proc(data: rawptr) { + player := transmute(^Player)data + player.can_dodge = true + }) + } + + if rl.IsMouseButtonDown(rl.MouseButton.LEFT) && can_shoot { + b := bullet_spawn(pos, dir) + append(&game.bullets, b) + can_shoot = false + timer_start(0.1, player, proc(data: rawptr) { + player := transmute(^Player)data + player.can_shoot = true + }) + } + } + + got_hit := false + + if !is_invulnerable && !is_dodging && !is_dead { + for segment in Segments { + if rl.Vector3DistanceSqrt(pos, segment.pos) < math.pow(radius + segment.radius, 2) { + got_hit = true + break + } + } + } + + if got_hit { + game.health -= 10 + is_invulnerable = true + timer_start(1, player, proc(data: rawptr) { + plr := transmute(^Player)data + plr.is_invulnerable = false + }) + if game.health <= 0 && !is_dead { + is_dead = true + } + } + +} + +player_draw :: proc(player: ^Player) { + using player + + dir_vector := get_vec_from_angle(dir) + color := rl.GREEN + if is_dodging { + color = rl.GRAY + } + if is_invulnerable { + color = rl.YELLOW + } + rl.DrawCircle3D(pos, radius, vec3up, 0, color) + rl.DrawLine3D(pos, pos + dir_vector * radius, rl.BLACK) + rl.DrawLine3D(pos, pos + vel, rl.RED) +} \ No newline at end of file diff --git a/player_bullets.odin b/player_bullets.odin new file mode 100644 index 0000000..698d13e --- /dev/null +++ b/player_bullets.odin @@ -0,0 +1,52 @@ +package main + +import rl "vendor:raylib" + +bullets : [dynamic]Bullet + +Bullet :: struct{ + pos: vec3, + vel: vec3, + radius: f32, + alive: bool, +} + +bullet_spawn :: proc(pos: vec3, dir: f32) -> Bullet { + return Bullet { + pos = pos, + vel = get_vec_from_angle(dir) * 70, + alive = true, + radius = 0.4 + } +} + +bullet_process :: proc(bullet: ^Bullet, game: ^Game, delta: f32) { + bullet.vel = rl.Vector3MoveTowards(bullet.vel, {}, 60 * delta) + bullet.pos += bullet.vel * delta + if rl.Vector3LengthSqr(bullet.vel) < 0.2 { + bullet.alive = false + } + + if rl.CheckCollisionCircles(bullet.pos.xy, bullet.radius, Head.pos.xy, Head.radius) { + bullet.alive = false + if game.snake_health == 0 { + Head.health -= 1 + } + return + } + for &segment in Segments { + if rl.CheckCollisionCircles(bullet.pos.xy, bullet.radius, segment.pos.xy, segment.collider_radius) { + bullet.alive = false + if segment.health > 0 { + segment.health -= 1 + if segment.health == 0 { + segment.collider_radius = 1.5 + } + } + } + } +} + +bullet_draw :: proc(bullet: Bullet) { + rl.DrawSphere(bullet.pos, bullet.radius, rl.WHITE) +} \ No newline at end of file diff --git a/snake.odin b/snake.odin new file mode 100644 index 0000000..7dcf15e --- /dev/null +++ b/snake.odin @@ -0,0 +1,263 @@ +package main + +import rl "vendor:raylib" +import "core:math" +import "core:fmt" +import "core:math/linalg" + +Snake_Health := 1000 + +Segments: [dynamic]^SnakeSegment +Head: SnakeHead + +SnakeState :: enum { + Chasing, + Chase_to_Dive, + Dive_to_Chase, + Diving, + Hunt, + Shot +} + +SnakeHead :: struct { + pos: vec3, + vel: vec3, + dir: f32, + radius: f32, + health: int, + max_health: int, + next: ^SnakeSegment, + state: SnakeState, + next_state: SnakeState, + state_timer: f32, + shot_timer: f32, + is_shooting: bool, + is_dead: bool, +} + +SnakeSegment :: struct { + pos: vec3, + vel: vec3, + dir: f32, + radius: f32, + collider_radius: f32, + active: bool, + health: u8, + head: ^SnakeHead, + next: ^SnakeSegment, + prev: ^SnakeSegment +} + +snake_spawn :: proc(pos: vec3, dir: f32, length: int){ + dir_vec := rl.Vector3RotateByAxisAngle(vec3right, vec3backward, dir) + + Head = SnakeHead{ + pos = pos, + dir = dir, + radius = 3, + state = .Chasing, + vel = dir_vec * 20, + health = 100, + max_health = 100, + state_timer = 5, + } + + for i := 0; i < length; i += 1 { + segment := new(SnakeSegment) + segment.active = false + segment.health = 3 + segment.radius = 2.5 + segment.collider_radius = 2.5 + segment.pos = pos - dir_vec * segment.radius + segment.dir = dir + segment.head = &Head + if i != 0 { + segment.prev = Segments[i-1] + Segments[i-1].next = segment + } else { + Head.next = segment + } + append(&Segments, segment) + } +} + +snake_clear :: proc() { + for segment in Segments { + free(segment) + } +} + +snake_process :: proc(game: ^Game, delta: f32) { + switch Head.state { + case .Chasing: + snake_chase_smooth(game, delta) + case .Diving: + snake_dive(game, delta) + case .Chase_to_Dive,.Dive_to_Chase: + snake_dropdown(game, delta) + case .Hunt: + snake_hunt(game, delta) + case .Shot: + snake_shot(game, delta) + } + total_health := 0 + for segment in Segments { + total_health += int(segment.health) + } + game.snake_health = total_health + if game.snake_health == 0 && Head.next != nil { + Head.next = nil + Head.state = .Hunt + Head.state_timer = 20 + fmt.println("Tail is dead") + return + } + for segment, i in Segments { + if segment.prev == nil && total_health == 0 { // Хвост падает, когда у него не осталось жизней + segment.vel.y -= 30 * delta + segment.pos += segment.vel * delta + continue + } + + target_pos := Head.pos + if segment.prev != nil { + target_pos = segment.prev.pos + } else { + segment.vel = Head.vel + } + diff := target_pos - segment.pos + if rl.Vector3Length(diff) > segment.radius { + segment.pos = target_pos - rl.Vector3Normalize(diff) * segment.radius + } + segment.dir = math.atan2(-diff.y, diff.x) + } +} + + +snake_chase :: proc(game: ^Game, delta: f32) { + using game + diff := player.pos - Head.pos + target_angle := math.atan2(-diff.y, diff.x) + dir_diff := angle_cycle(target_angle - Head.dir, -math.PI, math.PI) + Head.dir = angle_rotate(Head.dir, target_angle, min(abs(dir_diff), math.PI) * delta) + Head.vel = rl.Vector3RotateByAxisAngle(vec3right, vec3backward, Head.dir) * 500 + Head.pos += Head.vel * delta +} + +snake_chase_smooth :: proc(game: ^Game, delta: f32) { + using game + + if rl.IsKeyPressed(rl.KeyboardKey.K) { + for segment in Segments { + segment.health = 0 + } + } + + Head.state_timer -= delta + if Head.state_timer <= 0 { + Head.state = .Chase_to_Dive + Head.next_state = .Diving + Head.state_timer = 10 + } + + if Head.pos.y < -3 && Head.vel.y < 0 { + Head.pos.y = -3 + Head.vel.y = -Head.vel.y + } + + diff := player.pos - Head.pos + norm := rl.Vector3Normalize(diff) + Head.vel = rl.Vector3MoveTowards(Head.vel, norm * 70, 70 * delta) + Head.vel = rl.Vector3ClampValue(Head.vel, 30, 50) + Head.dir = math.atan2(-Head.vel.y, Head.vel.x) + Head.pos += Head.vel * delta +} + +snake_dive :: proc(game: ^Game, delta: f32) { + Head.state_timer -= delta + if Head.state_timer <= 0 { + Head.state = .Dive_to_Chase + Head.next_state = .Chasing + } + if Head.pos.y < 0 && Segments[len(Segments)-1].pos.y < 0 && Head.vel.y < 0 { + Head.pos.x = game.player.pos.x + Head.pos.y = -5 + Head.vel.x = 0 + Head.vel.y = 70 + } else { + grav : f32 = 20 + if Head.vel.y < 0 { grav = 30 } + else if Head.pos.y < game.player.pos.y { + grav = 7 + } + Head.vel.y -= grav * delta + Head.vel.x = (game.player.pos.x - Head.pos.x) * 2 + } + Head.dir = math.atan2(-Head.vel.y, Head.vel.x) + Head.pos += Head.vel * delta +} + +snake_dropdown :: proc(game: ^Game, delta: f32) { + Head.vel.y -= 100 * delta + Head.pos += Head.vel * delta + if Segments[len(Segments)-1].pos.y < 0 { + Head.state = Head.next_state + Head.state_timer = 20 + } +} + +snake_hunt :: proc(game: ^Game, delta: f32) { + Head.state_timer -= delta + if Head.state_timer <= 0 { + Head.state_timer = 10 + Head.state = .Shot + } + diff := game.player.pos - Head.pos + target_pos := game.player.pos + rl.Vector3Normalize(diff) * 10 + target_diff := target_pos - Head.pos + + Head.vel = rl.Vector3MoveTowards(Head.vel, rl.Vector3Normalize(target_diff) * 50, 40 * delta) + fmt.println(Head.vel) + Head.dir = math.atan2(-Head.vel.y, Head.vel.x) + Head.pos += Head.vel * delta +} + +snake_shot :: proc(game: ^Game, delta: f32) { + Head.state_timer -= delta + if Head.state_timer <= 0 { + Head.state_timer = 20 + Head.state = .Hunt + } + Head.is_shooting = false + if Head.state_timer < 8 && Head.state_timer > 3 { + Head.is_shooting = true + } + diff := game.player.pos - Head.pos + angle := math.atan2(-diff.y, diff.x) + + Head.dir = angle_rotate(Head.dir, angle, angle_cycle(abs(angle - Head.dir), -math.PI, math.PI) * delta * 2) + Head.vel = rl.Vector3MoveTowards(Head.vel, {}, 30 * delta) + Head.pos += Head.vel * delta +} + + +snake_draw :: proc(game: ^Game) { + + dir_vector := get_vec_from_angle(Head.dir) + rl.DrawCircle3D(Head.pos, Head.radius, vec3up, 0, rl.RED) + rl.DrawLine3D(Head.pos, Head.pos + dir_vector * Head.radius, rl.BLACK) + + if Head.is_shooting { + rl.DrawLine3D(Head.pos, Head.pos + get_vec_from_angle(Head.dir) * 300, rl.YELLOW) + } + + for segment in Segments { + dir_vector := get_vec_from_angle(segment.dir) + col := rl.RED + if segment.health == 0 { + col = rl.GRAY + } + rl.DrawCircle3D(segment.pos, segment.radius, vec3up, 0, col) + rl.DrawLine3D(segment.pos, segment.pos + dir_vector * segment.radius, rl.BLACK) + } +} \ No newline at end of file diff --git a/state.odin b/state.odin new file mode 100644 index 0000000..7f91fc6 --- /dev/null +++ b/state.odin @@ -0,0 +1,44 @@ +package main + + +import "core:slice" + +StateVariant :: union{^Game, ^Menu} + + +GameState :: struct { + update: proc(state: ^GameState, delta: f32), + draw: proc(state: ^GameState), + free: proc(state: ^GameState), + previous: ^GameState, + + variant: StateVariant +} + +new_state :: proc($T: typeid) -> ^T { + state := new(T) + state.variant = state + return state +} + +state_stack : [dynamic]^GameState + + + +stack_push :: proc(state: ^GameState) -> (bool) { + append(&state_stack, state) + return true +} + +stack_pop :: proc() -> (bool) { + if len(state_stack) == 0 { + return false + } + state := pop(&state_stack) + state->free() + return true +} + +stack_top :: proc() -> ^GameState { + return state_stack[len(state_stack)-1] +} diff --git a/timer.odin b/timer.odin new file mode 100644 index 0000000..f3ba85f --- /dev/null +++ b/timer.odin @@ -0,0 +1,63 @@ +package main + +import "core:math" +import "core:math/linalg" +import "core:slice" + + +Timer :: struct { + duration: f32, + time: f32, + active: bool, + finished: proc(data: rawptr), + data: rawptr +} + + +TIMER_SIZE :: 128 +timers : [dynamic]^Timer + +timers_clean :: proc() { + for timer, i in timers { + free(timer) + } +} + +timer_start :: proc( + time: f32, + data: rawptr = nil, + callback: proc(data: rawptr) = nil + ) -> ^Timer { + + timer := new(Timer) + timer.duration = time + timer.active = true + timer.data = data + timer.finished = callback + + append(&timers, timer) + return timer +} + +timer_cancel :: proc(t: ^Timer) { + t.active = false +} + +timers_process :: proc(delta: f32) { + #reverse for timer, i in timers { + if !timer.active { + free(timer) + unordered_remove(&timers, i) + continue + } + + timer.time += delta + + if timer.time >= timer.duration { + timer.active = false + if timer.finished != nil { + timer.finished(timer.data) + } + } + } +} diff --git a/tween.odin b/tween.odin new file mode 100644 index 0000000..08f42d3 --- /dev/null +++ b/tween.odin @@ -0,0 +1,80 @@ +package main + +import "core:math" +import "core:math/ease" +import "core:math/linalg" +import "core:slice" + + +Tween :: struct { + ptr: ^f32, + from: f32, + to: f32, + time: f32, + duration: f32, + ease_type: ease.Ease, + active: bool, + finished: proc(data: rawptr), + data: rawptr +} + + +tweens : [dynamic]^Tween + + +tween_clean :: proc() { + for tween, i in tweens { + free(tween) + } +} + +tween_to :: proc( + value: ^f32, to: f32, duration: f32, + ease: ease.Ease = ease.Ease.Quartic_In_Out, + data: rawptr = nil, + callback: proc(data: rawptr) = nil + ) -> ^Tween { + + tween := new(Tween) + tween.ptr = value + tween.from = value^ + tween.to = to + tween.duration = duration + tween.ease_type = ease + tween.active = true + tween.data = data + tween.finished = callback + + append(&tweens, tween) + return tween +} + +tween_cancel :: proc(t: ^Tween) { + t.active = false +} + +tweens_process :: proc(delta: f32) { + #reverse for tween, i in tweens { + + tween.time += delta + p := clamp(tween.time / tween.duration, 0, 1) + + val := ease.ease(tween.ease_type, p) + if tween.ptr != nil { + tween.ptr^ = math.lerp(tween.from, tween.to, val) + } else { + tween.active = false + } + + if tween.time >= tween.duration { + tween.active = false + if tween.finished != nil { + tween.finished(tween.data) + } + } + if !tween.active { + free(tween) + unordered_remove(&tweens, i) + } + } +} diff --git a/utils.odin b/utils.odin new file mode 100644 index 0000000..1020c99 --- /dev/null +++ b/utils.odin @@ -0,0 +1,32 @@ +package main + +import "core:math" +import "core:math/linalg" +import rl "vendor:raylib" + + +angle_cycle :: proc(value, min, max: f32) -> f32 { + delta := (max - min) + result := linalg.mod(value - min, delta) + if result < 0 { + result += delta + } + + return min + result +} + +angle_rotate :: proc(angle, target, speed: f32) -> f32 { + diff := angle_cycle(target - angle, -math.PI, math.PI) + if diff < -speed {return angle - speed} + if diff > speed {return angle + speed} + return target +} + +get_vec_from_angle :: proc(angle: f32) -> vec3 { + return rl.Vector3RotateByAxisAngle(vec3right, vec3backward, angle) +} + +draw_text_centered :: proc(font: rl.Font, text: cstring, pos: vec2, size: f32) { + text_size := rl.MeasureTextEx(font, text, size, 1) + rl.DrawTextPro(font, text, pos, text_size / 2, 0, size, 1, rl.BLACK) +}