Compare commits

..

11 Commits

Author SHA1 Message Date
be888397e4 feat: initial revision 2026-02-11 12:14:20 +01:00
Jeffery Myers
4cf96e771f add info about software render target 2026-01-13 11:47:28 -08:00
Jeffery Myers
759d3ff61a better filters for game and app resources 2026-01-07 18:49:51 -08:00
Jeffery Myers
3f4dfdc483 remove exe from common launch commands and let each platform define it's own 2026-01-07 08:53:30 -08:00
Jeffery Myers
c8e40f3845 Revise license section in README.md
Updated license information to reflect CC0 1.0.
2026-01-06 16:34:47 -08:00
Peter0x44
8853ece16a Update README.md 2026-01-06 23:18:43 +00:00
Jeffery Myers
75d4e7fc9f add debug task that does not regenerate the makefile 2025-12-11 09:52:03 -08:00
Jeffery Myers
31630dded8 add more clear description on how to get the template 2025-12-09 11:36:37 -08:00
Jeffery Myers
c880765945 Add support for generating compile_commands.json (#39) 2025-12-04 08:23:23 -08:00
Peter0x44
e5415bcb6e Add rename folder step to all platform sections
Fixes #30
2025-12-03 05:31:26 +00:00
Peter0x44
a6c1b96d0a Add support for generating compile_commands.json
- Integrate premake-ecc module for generating compile_commands.json
- Add .clangd config pointing to build/ directory
- Add 'Generate compile_commands.json' task
- Run compile_commands generation automatically on build
2025-12-03 05:16:23 +00:00
12 changed files with 971 additions and 43 deletions

2
.clangd Normal file
View File

@@ -0,0 +1,2 @@
CompileFlags:
CompilationDatabase: build/

1
.gitignore vendored
View File

@@ -405,3 +405,4 @@ Makefile
*.swp *.swp
*.swo *.swo
*.swn *.swn
/build/compile_commands.json

38
.vscode/launch.json vendored
View File

@@ -8,7 +8,6 @@
"name": "Debug", "name": "Debug",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}.exe",
"args": [], "args": [],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
@@ -23,7 +22,8 @@
} }
], ],
"windows": { "windows": {
"miDebuggerPath": "gdb.exe", "program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}.exe",
"miDebuggerPath": "gdb.exe"
}, },
"osx": { "osx": {
"program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}", "program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}",
@@ -36,7 +36,38 @@
"preLaunchTask": "build debug" "preLaunchTask": "build debug"
}, },
{ {
"name": "Run", "name": "Debug NoPremake",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": false
}
],
"windows": {
"program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}.exe",
"miDebuggerPath": "gdb.exe"
},
"osx": {
"program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}",
"MIMode": "lldb"
},
"linux": {
"program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}",
"miDebuggerPath": "/usr/bin/gdb",
},
"preLaunchTask": "build debug no premake"
},
{
"name": "Run Release",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"args": [], "args": [],
@@ -44,7 +75,6 @@
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"environment": [], "environment": [],
"externalConsole": false, "externalConsole": false,
"program": "${workspaceFolder}/bin/Release/${workspaceFolderBasename}.exe",
"MIMode": "gdb", "MIMode": "gdb",
"windows": { "windows": {
"program": "${workspaceFolder}/bin/Release/${workspaceFolderBasename}.exe", "program": "${workspaceFolder}/bin/Release/${workspaceFolderBasename}.exe",

45
.vscode/tasks.json vendored
View File

@@ -24,6 +24,26 @@
], ],
"dependsOn":["UpdateMake"] "dependsOn":["UpdateMake"]
}, },
{
"label": "build debug no premake",
"type": "process",
"command": "make",
"windows": {
"command": "mingw32-make.exe",
},
"osx": {
"args": [
"config=debug_arm64"
],
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{ {
"label": "build release", "label": "build release",
"type": "process", "type": "process",
@@ -95,7 +115,30 @@
"osx": { "osx": {
"command": "premake5.osx" "command": "premake5.osx"
}, },
"group": "build" "group": "build",
"dependsOn": ["Generate compile_commands.json"]
},
{
"label": "Generate compile_commands.json",
"type": "process",
"command": "./premake5",
"options": {
"cwd": "${workspaceFolder}/build/"
},
"args": [
"ecc"
],
"windows": {
"command": "./premake5.exe"
},
"linux": {
"command": "./premake5"
},
"osx": {
"command": "premake5.osx"
},
"group": "build",
"problemMatcher": []
} }
] ]
} }

View File

@@ -2,15 +2,36 @@
A simple cross platform template for setting up a project with the bleeding edge raylib code. A simple cross platform template for setting up a project with the bleeding edge raylib code.
Works with C or C++. Works with C or C++.
# Basic Setup
Download this repository to get started.
You can download the zip file of the repository from the Green Code button on github. This is the simplest way to get the template to start from.
Once you have downloaded the template, rename it to your project name.
or
Clone the repository with git, from the url
```
https://github.com/raylib-extras/raylib-quickstart.git
```
If you are using a command line git client you can use the command below to download and rename the template in one step
```
git clone https://github.com/raylib-extras/raylib-quickstart.git [name-for-your-project-here]
```
# Naming projects
* Replace the placeholder with your desired project name when running the git clone command above.
* __Do not name your game project 'raylib', it will conflict with the raylib library.__
* If you have used custom game name with __git clone__, there is no need to rename it again.
## Supported Platforms ## Supported Platforms
Quickstart supports the main 3 desktop platforms: Quickstart supports the main 3 desktop platforms:
* Windows * Windows
* Linux * Linux
* MacOS * MacOS
# Naming projects
Do not name your game project 'raylib', it will conflict with the raylib library.
# VSCode Users (all platforms) # VSCode Users (all platforms)
*Note* You must have a compiler toolchain installed in addition to vscode. *Note* You must have a compiler toolchain installed in addition to vscode.
@@ -23,9 +44,10 @@ Do not name your game project 'raylib', it will conflict with the raylib library
# Windows Users # Windows Users
There are two compiler toolchains available for windows, MinGW-W64 (a free compiler using GCC), and Microsoft Visual Studio There are two compiler toolchains available for windows, MinGW-W64 (a free compiler using GCC), and Microsoft Visual Studio
## Using MinGW-W64 ## Using MinGW-W64
* Rename the folder to your game name
* Double click the `build-MinGW-W64.bat` file * Double click the `build-MinGW-W64.bat` file
* CD into the folder in your terminal * CD into the folder in your terminal
* if you are usiing the W64devkit and have not added it to your system path environment variable, you must use the W64devkit.exe terminal, not CMD.exe * if you are using the W64devkit and have not added it to your system path environment variable, you must use the W64devkit.exe terminal, not CMD.exe
* If you want to use cmd.exe or any other terminal, please make sure that gcc/mingw-W64 is in your path environment variable. * If you want to use cmd.exe or any other terminal, please make sure that gcc/mingw-W64 is in your path environment variable.
* run `make` * run `make`
* You are good to go * You are good to go
@@ -34,7 +56,9 @@ There are two compiler toolchains available for windows, MinGW-W64 (a free compi
Make sure you have a modern version of MinGW-W64 (not mingw). Make sure you have a modern version of MinGW-W64 (not mingw).
The best place to get it is from the W64devkit from The best place to get it is from the W64devkit from
https://github.com/skeeto/w64devkit/releases https://github.com/skeeto/w64devkit/releases
or the version installed with the raylib installer or the version installed with the raylib installer
#### If you have installed raylib from the installer #### If you have installed raylib from the installer
Make sure you have added the path Make sure you have added the path
@@ -45,12 +69,14 @@ To your path environment variable so that the compiler that came with raylib can
DO NOT INSTALL ANOTHER MinGW-W64 from another source such as msys2, you don't need it. DO NOT INSTALL ANOTHER MinGW-W64 from another source such as msys2, you don't need it.
## Microsoft Visual Studio ## Microsoft Visual Studio
* Rename the folder to your game name
* Run `build-VisualStudio2022.bat` * Run `build-VisualStudio2022.bat`
* double click the `.sln` file that is generated * double click the `.sln` file that is generated
* develop your game * develop your game
* you are good to go * you are good to go
# Linux Users # Linux Users
* Rename the folder to your game name
* CD into the build folder * CD into the build folder
* run `./premake5 gmake` * run `./premake5 gmake`
* CD back to the root * CD back to the root
@@ -58,6 +84,7 @@ DO NOT INSTALL ANOTHER MinGW-W64 from another source such as msys2, you don't ne
* you are good to go * you are good to go
# MacOS Users # MacOS Users
* Rename the folder to your game name
* CD into the build folder * CD into the build folder
* run `./premake5.osx gmake` * run `./premake5.osx gmake`
* CD back to the root * CD back to the root
@@ -94,6 +121,13 @@ If you need to build for a different OpenGL version than the default (OpenGL 3.3
## For OpenGLES 3.0 ## For OpenGLES 3.0
`--graphics=opengles3` `--graphics=opengles3`
## For Software Rendering
`--graphics=software`
*Note*
Sofware rendering does not work with glfw, use Win32 or SDL platforms
`--backend=win32`
# Adding External Libraries # Adding External Libraries
Quickstart is intentionally minimal — it only includes what is required to compile and run a basic raylib project. Quickstart is intentionally minimal — it only includes what is required to compile and run a basic raylib project.
@@ -123,19 +157,5 @@ Different libraries will have different dependencies on different platforms.
# License # License
Copyright (c) 2020-2025 Jeffery Myers Raylib-Quickstart by Jeffery Myers is marked with CC0 1.0. To view a copy of this license, visit https://creativecommons.org/publicdomain/zero/1.0/
This software is provided "as-is", without any express or implied warranty. In no event
will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial
applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you
wrote the original software. If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented
as being the original software.
3. This notice may not be removed or altered from any source distribution.

29
build/ecc/LICENSE.md Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2022, Matvey Bystrin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

49
build/ecc/README.md Normal file
View File

@@ -0,0 +1,49 @@
# Export Compile Commands - ECC
Module implementing support for [JSON Compilation Database Format
Specification](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
This is an alternative to
[tarruda's](https://github.com/tarruda/premake-export-compile-commands) module,
which does one simple thing - generates one `compile_commands.json` file for
your project.
Tested with clangd-13.
## Requirements
Premake 5.0.0 or later.
## How to use
Download premake-ecc and place it near your premake5.lua script. Then require it
from your premake script
```lua
require "ecc/ecc"
```
After you simply can call:
```
premake5 ecc
```
Moldule will generate **one** `compile_commands.json` file near your main
premake script. During generation it will use the default config (the first one
you have specified in script). If you want to select specific config just pass
it's name with command line option:
```
premake5 --config=release ecc
```
Careful! `config` option case sensitive! If there is no config passed via
command line, module will choose the default one.
Note: if you want to embed this module into your premake build follow
the [manual](https://premake.github.io/docs/Embedding-Modules/)
## Future plans
- Add unit tests
## Alternatives
- [export-compile-commands](https://github.com/tarruda/premake-export-compile-commands)
- [bear](https://github.com/rizsotto/Bear)

4
build/ecc/_manifest.lua Normal file
View File

@@ -0,0 +1,4 @@
return {
"_preload.lua",
"ecc.lua"
}

35
build/ecc/_preload.lua Normal file
View File

@@ -0,0 +1,35 @@
local p = premake
newoption {
trigger = "config",
value = "CFG",
description = "Select config for export compile_commands.json"
}
newaction {
trigger = "ecc",
shortname = "Export compile commands",
description = "Export compile_commands.json for language server",
toolset = "gcc",
valid_kinds = { "ConsoleApp", "WindowedApp", "StaticLib", "SharedLib" },
valid_languages = { "C", "C++" },
valid_tools = {
cc = { "clang", "gcc" }
},
onStart = function()
p.indent(" ")
end,
execute = function()
local dir = {}
dir.location = _MAIN_SCRIPT_DIR
p.generate(dir, "compile_commands.json", p.modules.ecc.generateFile)
end
}
return function(cfg)
return (_ACTION == "ecc")
end

122
build/ecc/ecc.lua Normal file
View File

@@ -0,0 +1,122 @@
-- Include module if it is not embedded
if premake.modules.ecc == nil then
include ( "_preload.lua" )
end
local p = premake
local project = p.project
p.modules.ecc = {}
local m = p.modules.ecc
m._VERSION = "1.0.1-alpha"
function m.generateFile()
p.push("[")
for wks in p.global.eachWorkspace() do
for prj in p.workspace.eachproject(wks) do
m.onProject(prj)
end
end
p.pop("]")
end
function m.onProject(prj)
if project.isc(prj) or project.iscpp(prj) then
local cfg = m.getConfig(prj)
local args = m.getArguments(prj, cfg)
local files = table.shallowcopy(prj._.files)
for i,node in ipairs(files) do
local output = cfg.objdir .. "/" .. node.objname .. ".o"
local obj = path.getrelative(prj.location, output)
p.push("{")
p.push("\"arguments\": [")
m.writeArgs(args, obj, node.relpath)
p.pop("],")
p.w("\"directory\": \"%s\",", prj.location)
p.w("\"file\": \"%s\",", node.abspath)
p.w("\"output\": \"%s\"", output)
p.pop("},")
end
end
end
function m.writeArgs(args, obj, src)
for _,arg in ipairs(args) do
-- Defines like the following will break JSON format, quotes need to be escaped
-- -DEXPORT_API=__attribute__((visibility("default")))
local escaped = arg:gsub("\"", "\\\"")
p.w("\"%s\",", escaped)
end
p.w("\"-c\",")
p.w("\"-o\",")
p.w("\"%s\",", obj)
p.w("\"%s\"", src)
end
function m.getConfig(prj)
local ocfg = _OPTIONS.config
local cfg = {}
if ocfg and prj.configs[ocfg] then
cfg = prj.configs[ocfg]
else
cfg = m.defaultconfig(prj)
end
return cfg
end
function m.getArguments(prj, cfg)
local toolset = m.getToolSet(cfg)
local args = {}
local tool = iif(project.iscpp(prj), "cxx", "cc")
local toolname = iif(cfg.prefix, toolset.gettoolname(cfg, tool), toolset.tools[tool])
args = table.join(args, toolname)
args = table.join(args, toolset.getcppflags(cfg)) -- Preprocessor
args = table.join(args, toolset.getdefines(cfg.defines))
args = table.join(args, toolset.getundefines(cfg.undefines))
args = table.join(args, toolset.getincludedirs(cfg, cfg.includedirs, cfg.sysincludedirs))
if project.iscpp(prj) then
args = table.join(args, toolset.getcxxflags(cfg))
else
args = table.join(args, toolset.getcflags(cfg))
end
args = table.join(args, cfg.buildoptions)
return args
end
-- Copied from gmake2 module
-- Return default toolset of given config or system default toolset
function m.getToolSet(cfg)
local default = iif(cfg.system == p.MACOSX, "clang", "gcc")
local toolset = p.tools[_OPTIONS.cc or cfg.toolset or default]
if not toolset then
error("Invalid toolset '" .. cfg.toolset .. "'")
end
return toolset
end
-- Copied from gmake2 module
function m.defaultconfig(target)
-- find the right configuration iterator function for this object
local eachconfig = iif(target.project, project.eachconfig, p.workspace.eachconfig)
local defaultconfig = nil
-- find the right default configuration platform, grab first configuration that matches
if target.defaultplatform then
for cfg in eachconfig(target) do
if cfg.platform == target.defaultplatform then
defaultconfig = cfg
break
end
end
end
-- grab the first configuration and write the block
if not defaultconfig then
local iter = eachconfig(target)
defaultconfig = iter()
end
return defaultconfig
end

View File

@@ -1,3 +1,6 @@
-- Export Compile Commands module for clangd support
require "ecc/ecc"
newoption newoption
{ {
trigger = "graphics", trigger = "graphics",
@@ -189,13 +192,15 @@ if (downloadRaylib) then
{ {
["Header Files/*"] = { "../include/**.h", "../include/**.hpp", "../src/**.h", "../src/**.hpp"}, ["Header Files/*"] = { "../include/**.h", "../include/**.hpp", "../src/**.h", "../src/**.hpp"},
["Source Files/*"] = {"../src/**.c", "src/**.cpp"}, ["Source Files/*"] = {"../src/**.c", "src/**.cpp"},
["Windows Resource Files/*"] = {"../src/**.rc", "src/**.ico"}, ["Windows Resource Files/*"] = {"../src/**.rc", "../src/**.ico"},
["Game Resource Files/*"] = {"../resources/**"},
} }
files {"../src/**.c", "../src/**.cpp", "../src/**.h", "../src/**.hpp", "../include/**.h", "../include/**.hpp"} files {"../src/**.c", "../src/**.cpp", "../src/**.h", "../src/**.hpp", "../include/**.h", "../include/**.hpp"}
filter {"system:windows", "action:vs*"} filter {"system:windows", "action:vs*"}
files {"../src/*.rc", "../src/*.ico"} files {"../src/*.rc", "../src/*.ico"}
files {"../resources/**"}
filter{} filter{}

View File

@@ -1,23 +1,462 @@
/* /*
Raylib example file. Shooting arrows - ya know
This is an example main file for a simple raylib project.
Use this as a starting point or replace it with your code.
by Jeffery Myers is marked with CC0 1.0. To view a copy of this license, visit https://creativecommons.org/publicdomain/zero/1.0/
*/ */
#include "raylib.h" #include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "resource_dir.h" // utility header for SearchAndSetResourceDir #include "raylib.h"
#include "resource_dir.h"
const float G = 458.0;
const float attritionCoefficient = 95.0;
const int archerSize = 10;
const float archerSpeed = 96.0;
const float maxBowTension = 921.0;
const float bowTensionSpeed = 12.0;
const float spacing = 16;
const float bowTensionIndicatorWidth = 360;
const float bowTensionIndicatorHeight = 40;
const float arrowLength = 20.0f;
const int groundHeight = 40;
#define MAX_BLOOD_PARTICLES 800
// Utility function to map a value from one range to another
float map(float value, float fromLow, float fromHigh, float toLow, float toHigh)
{
return toLow + (toHigh - toLow) * (value - fromLow) / (fromHigh - fromLow);
}
// Utility function to normalize a Vector2
Vector2 normalized(Vector2 v)
{
float len = sqrtf(v.x * v.x + v.y * v.y);
if (len == 0.0f)
{
Vector2 zero = {0.0f, 0.0f};
return zero;
}
Vector2 n = {v.x / len, v.y / len};
return n;
}
typedef struct Target Target;
typedef struct Arrow Arrow;
struct Arrow
{
Vector2 position;
Vector2 speed;
Vector2 acceleration;
Vector2 direction;
bool stuck;
Target *hitTarget;
};
typedef struct ArrowNode ArrowNode;
struct ArrowNode
{
Arrow *data;
ArrowNode *prev;
ArrowNode *next;
};
// Bleed pattern: alternating durations of squirt (spawn particles) and pause
// {squirt, pause, squirt, pause, squirt, pause, squirt,..., 0} — 0 marks end
#define BLEED_PHASES 18
const float bleedPattern[BLEED_PHASES] = {0.08f, 0.06f, 0.38f, 0.25f, 0.08f, 2.06f, 0.38f, 0.25f, 0.10f, 1.06f, 0.08f, 0.3f, 2.2f, 0.4f, 1.1f, 0.9f, 0.8f, 0.0f};
// even indices = squirt, odd indices = pause, last 0 = done
typedef struct
{
Vector2 position;
Vector2 velocity;
float lifetime;
float maxLifetime;
bool active;
} BloodParticle;
BloodParticle bloodParticles[MAX_BLOOD_PARTICLES];
void spawn_blood(Vector2 origin, Vector2 direction, int count)
{
for (int i = 0; i < MAX_BLOOD_PARTICLES && count > 0; i++)
{
if (!bloodParticles[i].active)
{
bloodParticles[i].active = true;
bloodParticles[i].position = origin;
// base direction is opposite to the arrow's flight
float baseAngle = atan2f(-direction.y, -direction.x);
// spread within ~90 degrees of the opposite direction
float spread = ((float)GetRandomValue(-45, 45)) * DEG2RAD;
float angle = baseAngle + spread;
float speed = (float)GetRandomValue(0, 320);
bloodParticles[i].velocity.x = cosf(angle) * speed;
bloodParticles[i].velocity.y = sinf(angle) * speed;
bloodParticles[i].maxLifetime = 0.4f + (float)GetRandomValue(10, 475) / 100.0f;
bloodParticles[i].lifetime = bloodParticles[i].maxLifetime;
count--;
}
}
}
void update_blood(float deltaTime)
{
for (int i = 0; i < MAX_BLOOD_PARTICLES; i++)
{
if (!bloodParticles[i].active)
continue;
bloodParticles[i].lifetime -= deltaTime;
if (bloodParticles[i].lifetime <= 0)
{
bloodParticles[i].active = false;
continue;
}
bloodParticles[i].velocity.y += G * 0.5f * deltaTime;
bloodParticles[i].position.x += bloodParticles[i].velocity.x * deltaTime;
bloodParticles[i].position.y += bloodParticles[i].velocity.y * deltaTime;
}
}
void draw_blood(void)
{
for (int i = 0; i < MAX_BLOOD_PARTICLES; i++)
{
if (!bloodParticles[i].active)
continue;
float alpha = bloodParticles[i].lifetime / bloodParticles[i].maxLifetime;
unsigned char a = (unsigned char)(alpha * 255);
Color c = {200, 0, 0, a};
float size = 2.0f + alpha * 2.0f;
DrawRectangle(
(int)(bloodParticles[i].position.x - size / 2),
(int)(bloodParticles[i].position.y - size / 2),
(int)size, (int)size, c);
}
}
struct Target
{
Vector2 position;
Vector2 speed;
bool bleeding;
bool grounded;
int bleedPhase;
float bleedTimer;
};
typedef struct TargetNode TargetNode;
struct TargetNode
{
Target *data;
TargetNode *prev;
TargetNode *next;
};
void add_target(TargetNode *head, Vector2 position)
{
TargetNode *el = head;
while (el->next)
{
el = el->next;
}
Target *t = malloc(sizeof(Target));
t->position = position;
t->speed.x = 0;
t->speed.y = 0;
t->bleeding = false;
t->grounded = false;
t->bleedPhase = 0;
t->bleedTimer = 0;
TargetNode *newNode = (TargetNode *)malloc(sizeof(TargetNode));
newNode->data = t;
newNode->prev = el;
newNode->next = NULL;
el->next = newNode;
}
void remove_target(TargetNode *node)
{
node->prev->next = node->next;
if (node->next)
node->next->prev = node->prev;
free(node->data);
free(node);
}
void fire_arrow(ArrowNode *head, Vector2 origin, Vector2 acceleration)
{
if (!head)
{
TraceLog(LOG_ERROR, "Cannot fire arrow: head pointer is NULL");
return;
}
ArrowNode *el = head;
while (el->next)
{
el = el->next;
}
Arrow *a = malloc(sizeof(Arrow));
a->position.x = origin.x;
a->position.y = origin.y;
a->speed.x = acceleration.x;
a->speed.y = acceleration.y;
a->acceleration.x = 0;
a->acceleration.y = 0;
a->direction = normalized(a->speed);
a->stuck = false;
a->hitTarget = NULL;
ArrowNode *newNode = (ArrowNode *)malloc(sizeof(ArrowNode));
newNode->data = a;
newNode->prev = el;
newNode->next = NULL;
el->next = newNode;
}
void update_arrows(ArrowNode *head, TargetNode *targets, Texture targetTex, float deltaTime, float attrition)
{
Rectangle groundRect = {.x = 0, .y = GetScreenHeight() - groundHeight, .width = GetScreenWidth(), .height = groundHeight};
ArrowNode *el = head->next;
while (el != NULL)
{
Arrow *a = el->data;
// skip stuck arrows
if (a->stuck)
{
el = el->next;
continue;
}
a->speed.x += a->acceleration.x * deltaTime;
a->speed.y += a->acceleration.y * deltaTime;
// apply gravity directly to speed
a->speed.y += G * deltaTime;
a->position.x += a->speed.x * deltaTime;
a->position.y += a->speed.y * deltaTime;
// update flight direction
a->direction = normalized(a->speed);
// to simulate attrition will scale down the acceleration by a constant
float decay = powf(attrition, deltaTime);
a->acceleration.x *= decay;
a->acceleration.y *= decay;
// check for target collisions
TargetNode *tEl = targets->next;
while (tEl != NULL)
{
Target *t = tEl->data;
if (t->bleeding)
{
tEl = tEl->next;
continue;
}
Rectangle targetRect = {t->position.x, t->position.y, (float)targetTex.width, (float)targetTex.height};
if (CheckCollisionPointRec(a->position, targetRect))
{
t->bleeding = true;
t->bleedPhase = 0;
t->bleedTimer = bleedPattern[0];
// penetrate into the target proportionally to hit speed
float hitSpeed = sqrtf(a->speed.x * a->speed.x + a->speed.y * a->speed.y);
float maxPenetration = arrowLength * 1.1f;
float penetration = (hitSpeed / maxBowTension) * maxPenetration;
a->position.x += a->direction.x * penetration;
a->position.y += a->direction.y * penetration;
a->acceleration.x = a->acceleration.y = a->speed.x = a->speed.y = 0;
a->stuck = true;
a->hitTarget = t;
TraceLog(LOG_INFO, "Target hit!");
break;
}
tEl = tEl->next;
}
if (a->stuck)
{
el = el->next;
continue;
}
// check for ground collision
if (CheckCollisionPointRec(a->position, groundRect))
{
a->position.y = GetScreenHeight() - groundHeight;
a->acceleration.x = a->acceleration.y = a->speed.x = a->speed.y = 0;
a->stuck = true;
}
// if arrow went offscreen, remove it
if (
(a->position.x > GetScreenWidth() || a->position.x < 0) ||
a->position.y > GetScreenHeight())
{
el->prev->next = el->next;
if (el->next)
el->next->prev = el->prev;
ArrowNode *orphan = el;
el = el->next;
free(orphan->data);
free(orphan);
TraceLog(LOG_INFO, "Removing arrow");
}
else
{
el = el->next;
}
}
}
void remove_arrow(ArrowNode *node)
{
node->prev->next = node->next;
if (node->next)
node->next->prev = node->prev;
free(node->data);
free(node);
}
void update_targets(TargetNode *targets, ArrowNode *arrows, Texture targetTex, float deltaTime)
{
TargetNode *tEl = targets->next;
while (tEl != NULL)
{
Target *t = tEl->data;
if (!t->bleeding)
{
tEl = tEl->next;
continue;
}
// apply gravity and move the target while falling
if (!t->grounded)
{
t->speed.y += G * deltaTime;
float dx = t->speed.x * deltaTime;
float dy = t->speed.y * deltaTime;
t->position.x += dx;
t->position.y += dy;
float groundY = GetScreenHeight() - groundHeight - targetTex.height;
if (t->position.y >= groundY)
{
dy -= (t->position.y - groundY);
t->position.y = groundY;
t->speed.x = 0;
t->speed.y = 0;
t->grounded = true;
}
// move stuck arrows along with the target
ArrowNode *aEl = arrows->next;
while (aEl != NULL)
{
if (aEl->data->hitTarget == t)
{
aEl->data->position.x += dx;
aEl->data->position.y += dy;
}
aEl = aEl->next;
}
}
t->bleedTimer -= deltaTime;
if (t->bleedTimer > 0)
{
// during squirt phases (even indices), spawn particles
if (t->bleedPhase % 2 == 0)
{
Vector2 center = {
t->position.x + targetTex.width / 2.0f,
t->position.y + targetTex.height / 2.0f};
ArrowNode *aEl = arrows->next;
while (aEl != NULL)
{
ArrowNode *aNext = aEl->next;
if (aEl->data->hitTarget == t)
{
spawn_blood(aEl->data->position, aEl->data->direction, MAX_BLOOD_PARTICLES / 2);
}
aEl = aNext;
}
}
tEl = tEl->next;
}
else
{
// advance to next phase
t->bleedPhase++;
if (t->bleedPhase >= BLEED_PHASES || bleedPattern[t->bleedPhase] == 0.0f)
{
// remove arrows that hit this target
ArrowNode *aEl = arrows->next;
while (aEl != NULL)
{
ArrowNode *aNext = aEl->next;
if (aEl->data->hitTarget == t)
{
remove_arrow(aEl);
}
aEl = aNext;
}
// animation done, remove target and spawn a new one
TargetNode *dead = tEl;
tEl = tEl->next;
remove_target(dead);
Vector2 newPos = {
(float)GetRandomValue(0, GetScreenWidth() - targetTex.width),
(float)GetRandomValue(0, GetScreenHeight() / 2)};
add_target(targets, newPos);
TraceLog(LOG_INFO, "Target removed after bleeding, new target spawned");
}
else
{
t->bleedTimer = bleedPattern[t->bleedPhase];
tEl = tEl->next;
}
}
}
}
int main() int main()
{ {
// Tell the window to use vsync and work on high DPI displays // Tell the window to use vsync and work on high DPI displays
SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI); SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI | FLAG_WINDOW_RESIZABLE);
// Create the window and OpenGL context // Create the window and OpenGL context
InitWindow(1280, 800, "Hello Raylib"); InitWindow(1280, 800, "Archer");
// Utility function from resource_dir.h to find the resources folder and set it as the current working directory so we can load from it // Utility function from resource_dir.h to find the resources folder and set it as the current working directory so we can load from it
SearchAndSetResourceDir("resources"); SearchAndSetResourceDir("resources");
@@ -25,26 +464,175 @@ int main ()
// Load a texture from the resources directory // Load a texture from the resources directory
Texture wabbit = LoadTexture("wabbit_alpha.png"); Texture wabbit = LoadTexture("wabbit_alpha.png");
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
Vector2 mousePosition = {0, 0};
char coords[200];
bool trajectoryVisible = false;
Vector2 archerPosition = {screenWidth / 2 - archerSize / 2, screenHeight - groundHeight - archerSize};
float bowTension = 0.0;
ArrowNode *head = (ArrowNode *)calloc(1, sizeof(ArrowNode));
TargetNode *targets = (TargetNode *)calloc(3, sizeof(TargetNode));
for (int i = 0; i < 3; i++)
{
// spawn targets at a random position in the top region
Vector2 targetPos = {
(float)GetRandomValue(0, screenWidth - wabbit.width),
(float)GetRandomValue(0, screenHeight / 3)};
add_target(targets, targetPos);
}
float dt = 0;
// game loop // game loop
while (!WindowShouldClose()) // run the loop until the user presses ESCAPE or presses the Close button on the window while (!WindowShouldClose()) // run the loop until the user presses ESCAPE or presses the Close button on the window
{ {
dt = GetFrameTime();
if (IsWindowResized())
{
screenWidth = GetScreenWidth();
screenHeight = GetScreenHeight();
}
mousePosition = GetMousePosition();
sprintf(coords, "(x=%1.0f, y=%1.0f)", mousePosition.x, mousePosition.y);
float tensionRate = -3.0;
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT))
{
tensionRate = 1.0;
}
bowTension += bowTensionSpeed * tensionRate;
if (bowTension < 0)
{
bowTension = 0;
}
if (bowTension > maxBowTension)
{
bowTension = maxBowTension;
}
// Pressing the right mouse button releases the bow istantly
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT))
{
TraceLog(LOG_INFO, "Arrow launched");
Vector2 dir = {mousePosition.x - archerPosition.x, mousePosition.y - archerPosition.y};
Vector2 direction = normalized(dir);
Vector2 force = {direction.x * bowTension, direction.y * bowTension};
fire_arrow(head, archerPosition, force);
bowTension = 0;
}
if (IsKeyPressed(KEY_T))
{
trajectoryVisible = !trajectoryVisible;
TraceLog(LOG_INFO, "Toggling trajectory indicators");
}
if (IsKeyDown(KEY_A))
{
archerPosition.x -= archerSpeed * dt;
}
if (IsKeyDown(KEY_D))
{
archerPosition.x += archerSpeed * dt;
}
update_arrows(head, targets, wabbit, dt, attritionCoefficient);
update_targets(targets, head, wabbit, dt);
update_blood(dt);
// drawing // drawing
BeginDrawing(); BeginDrawing();
// Setup the back buffer for drawing (clear color and depth buffers) // Setup the back buffer for drawing (clear color and depth buffers)
ClearBackground(BLACK); ClearBackground(BLACK);
// draw some text using the default font // Draw ground
DrawText("Hello Raylib", 200,200,20,WHITE); DrawRectangle(0, screenHeight - groundHeight, screenWidth, groundHeight, BROWN);
// draw our texture to the screen // draw targets
DrawTexture(wabbit, 400, 200, WHITE); TargetNode *drawTarget = targets->next;
while (drawTarget != NULL)
{
Target *t = drawTarget->data;
DrawTexture(wabbit, (int)t->position.x, (int)t->position.y, WHITE);
drawTarget = drawTarget->next;
}
// draw blood particles
draw_blood();
// for now the archer will be a simple square
DrawRectangle(archerPosition.x, archerPosition.y, archerSize, archerSize, GREEN);
// draw arrows
ArrowNode *drawEl = head->next;
while (drawEl != NULL)
{
Arrow *a = drawEl->data;
Vector2 dir = a->direction;
Vector2 tail = {a->position.x - dir.x * arrowLength, a->position.y - dir.y * arrowLength};
DrawLineEx(tail, a->position, 2.0f, YELLOW);
drawEl = drawEl->next;
}
if (trajectoryVisible)
{
// aim hints
Vector2 startY = {0, mousePosition.y};
DrawLineDashed(startY, mousePosition, 4, 8, GRAY);
Vector2 startX = {mousePosition.x, screenHeight};
DrawLineDashed(startX, mousePosition, 4, 8, GRAY);
Vector2 bowOrigin = {archerPosition.x + archerSize / 2, archerPosition.y + archerSize / 2};
DrawLineDashed(bowOrigin, mousePosition, 4, 8, GRAY);
// targeting coordinates
DrawText(coords, screenWidth - bowTensionIndicatorWidth - spacing, bowTensionIndicatorHeight + 2 * spacing, 24, WHITE);
}
// Tension indicator
DrawRectangleLines(screenWidth - bowTensionIndicatorWidth - spacing, spacing, bowTensionIndicatorWidth, bowTensionIndicatorHeight, WHITE);
DrawRectangle(screenWidth - bowTensionIndicatorWidth - spacing, spacing, map(bowTension, 0, maxBowTension, 0, bowTensionIndicatorWidth), bowTensionIndicatorHeight, WHITE);
// end the frame and get ready for the next one (display frame, poll input, etc...) // end the frame and get ready for the next one (display frame, poll input, etc...)
EndDrawing(); EndDrawing();
} }
// cleanup // cleanup
// free arrow linked list
ArrowNode *el = head;
while (el != NULL)
{
ArrowNode *next = el->next;
free(el->data);
free(el);
el = next;
}
// free target linked list
TargetNode *tEl = targets;
while (tEl != NULL)
{
TargetNode *next = tEl->next;
free(tEl->data);
free(tEl);
tEl = next;
}
// unload our texture so it can be cleaned up // unload our texture so it can be cleaned up
UnloadTexture(wabbit); UnloadTexture(wabbit);