Compare commits

3 Commits

Author SHA1 Message Date
Gus
df79adba2e Name update 2025-07-29 10:28:13 -03:00
Gus
df8e4eeecb Correct a bug on the time calculation 2025-07-23 17:45:18 -03:00
Gus
2674d42376 Update README.md 2025-07-22 23:39:22 -03:00
3 changed files with 191 additions and 76 deletions

View File

@@ -1,6 +1,6 @@
# Virtua_Pet
This is a tiny little project I made to get my own Virtual Pet. I hope you like it as much as I do.
The code can receive some optimiztion, so feel free to change it as you like.
The code was written in the Arduino IDE and can receive some optimiztion, so feel free to change it as you like.
All the bitmap sprites are on the bitmap.h file.
I would be glade to see your project too if you choose to make one pet by yourself.
Thank you!

BIN
schematics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@@ -11,6 +11,8 @@ HARDWARE:
- 1.3" OLED I2C Screen 128x64;
- 4 push buttons (with 1n4148 diode to prevent debounce);
NOTE: Disable the USB serial communication for more ROM space
by Gus Costa
(GitHub: gusocosta)
(Reddit: gu-ocosta)
@@ -19,6 +21,8 @@ by Gus Costa
#include <GyverOLED.h>
#include "bitmaps.h"
//Choose your screen protocol
//GyverOLED<SSD1306_128x64> oled;
GyverOLED<SSH1106_128x64> oled;
//Peripheral constants
@@ -28,6 +32,7 @@ const uint8_t bt_up = PA11;
const uint8_t bt_left = PA12;
//Time vars and const
bool timer_rolling = false;
const uint8_t check_pet_time = 5; //in sec
uint32_t draw_tick = 0;
uint8_t draw_tick_max = 100;
@@ -75,16 +80,23 @@ uint8_t pet_mood = 0; //0: normal, 1: happy, 2: sleep, 3:dead
uint8_t pet_doing = 0; //0: nothing, 1: eating tomato, 2: eating carrot, 3: eating mushroom, 4: eating chicken, 5: eating candy, 6: cleaning, 7: petting, 8: sleeping, 9: dead
bool pet_alive = true;
bool pet_sleeping = false;
bool pet_poop = false;
bool pet_poop = true;
uint8_t pet_ani = 0;
uint8_t food_check[5] = {0,0,0,0,0};
uint8_t food_check[5] = {0,0,0,0,0}; //0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
bool happy_fail = false; //vars to check for evolution
bool health_fail = false; //vars to check for evolution
//Name vars and consts
char ascii_alphabet[63] = {}; //all the alphabet. 26 Uppercases, 26 lowercases, 10 numbers and underscore
char name_letters_array[8] = {95,95,95,95,95,95,95,95}; //the characters that compose the pet's name
String pet_name = "";
uint8_t ascii_letter_count[8] = {62,62,62,62,62,62,62,62};
uint8_t name_letters_indicator = 0; //0 to 7 for the letters, 8 for the "Next" button
//UI vars and consts
uint32_t rng_comp = 0;
int rng_dir = 1;
uint8_t screen = 1; //0: name, 1: main, 2: status
uint8_t screen = 0; //0: name, 1: main, 2: status
uint8_t sel = 0;
uint8_t food_sel = 0; //0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
const uint8_t bottom_bar_size = 5;
@@ -118,47 +130,43 @@ const unsigned char* trex_up[1][2] = {{trex_up1, trex_up2}};
const unsigned char* trex_eyes[3][2] = {{trex_eye_n1, trex_eye_n2},{trex_eye_h1, trex_eye_h2},{trex_eye_s1, trex_eye_s2}};
void setup() {
Serial.begin(9600);
delay(10);
pinMode(bt_left, INPUT_PULLUP);
pinMode(bt_right, INPUT_PULLUP);
pinMode(bt_up, INPUT_PULLUP);
pinMode(bt_down, INPUT_PULLUP);
//pinMode(buzz, OUTPUT);
oled.init();
oled.clear();
bottom_bar_gen();
age_gen();
alphabet_gen();
rng_comp = int(millis()+analogRead(PA0));
if (rng_comp % 2 == 0){pet_sex = 1;}
}
// Generates the bottom bar
void bottom_bar_gen(){
bottom_bar_sel_size = 127 / bottom_bar_size;
for(uint8_t i = 0; i < bottom_bar_size; i++){
bottom_bar_pos[i] = (i* bottom_bar_sel_size) + ((bottom_bar_sel_size - 8)/2);
//========================================================================
//============================ Name Functions ============================
//========================================================================
void alphabet_gen(){
uint8_t char_count = 65;
for (uint8_t i=0; i<26; i++){ //Uppercase
ascii_alphabet[i] = char_count + i;
}
char_count = 97;
for (uint8_t i=0; i<26; i++){ //Lowercase
ascii_alphabet[26 + i] = char_count + i;
}
char_count = 48;
for (uint8_t i=0; i<10; i++){ //numbers
ascii_alphabet[52 + i] = char_count + i;
}
ascii_alphabet[62] = 95; //add space
}
// Make the age string on the status screen
void age_gen(){
if (pet_alive == true){
if (act_time[3]!=0){
pet_age = String(act_time[3]) + "D";
}
else if (act_time[2]!=0){
pet_age = String(act_time[2]) + "H";
}
else if (act_time[1]!=0){
pet_age = String(act_time[1]) + "m";
}
else{
pet_age = "0m";
}
}
}
//========================================================================
//============================= Time Function ============================
//========================================================================
@@ -201,31 +209,56 @@ void time_check(){
if (act_time[1] >= 60){ //check hours
act_time[1] = 0;
act_time[2] += 1;
if (act_time[1] >= 24){ //check days
act_time[2] = 0;
act_time[3] += 1;
if (pet_lvl == 1){
evolve_ani = true;
}
}
}
}
if (act_time[2] >= 24){ //check days
act_time[2] = 0;
act_time[3] += 1;
if (pet_lvl == 1){
evolve_ani = true;
}
}
}
}
}
//========================================================================
//================================ Buttons ===============================
//========================================================================
void check_bt(){
//========================================================================
//=============================== Button Up ==============================
//========================================================================
if (digitalRead(bt_up) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1;
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis();
//Name Screen
if (screen == 0){
if(name_letters_indicator !=8){
ascii_letter_count[name_letters_indicator] = func_select(ascii_letter_count[name_letters_indicator], 62, 1);
name_letters_array[name_letters_indicator] = ascii_alphabet[ascii_letter_count[name_letters_indicator]];
}
else{
uint8_t letter_count = 0;
for(uint8_t i =0; i < 8; i++){ //Counting the written letters
if(name_letters_array[7-i] != 95){
letter_count = 7-i;
break;
}
}
for(uint8_t i =0; i <= letter_count; i++){ //Saving the name
pet_name += name_letters_array[i];
}
timer_rolling = true;
screen = 1;
}
}
//Main Screen
if (screen == 1){
else if (screen == 1){
if (pet_alive == true && doing_tick < millis()){
if (sel == 0){ //Eating
if (pet.hungry < 100){
@@ -238,7 +271,7 @@ void check_bt(){
pet.happy += pet_eating_vars[pet_species][food_sel][3];
pet.sleep += pet_eating_vars[pet_species][food_sel][4];
food_check[food_sel] += 1;
food_check[food_sel] += 1; //0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
pet_mood = 1;
pet_sleeping = false;
}
@@ -292,9 +325,7 @@ void check_bt(){
}
}
//========================================================================
//============================== Button Down =============================
//========================================================================
if (digitalRead(bt_down) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1;
@@ -302,7 +333,16 @@ void check_bt(){
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis();
if (screen == 1){
//Name Screen
if (screen == 0){
if(name_letters_indicator !=8){
ascii_letter_count[name_letters_indicator] = func_select(ascii_letter_count[name_letters_indicator], 62, 0);
name_letters_array[name_letters_indicator] = ascii_alphabet[ascii_letter_count[name_letters_indicator]];
}
}
//Main Screen
else if (screen == 1){
if (pet_alive == true && doing_tick < millis()){
if (sel == 0){ //Eating
food_sel = func_select(food_sel, 4, 1);
@@ -311,20 +351,27 @@ void check_bt(){
}
}
//Status Screen
else if (screen == 2){
screen = 1;
}
}
}
//========================================================================
//============================== Button Left =============================
//========================================================================
if (digitalRead(bt_left) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1;
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis();
//Name Screen
if (screen == 0){
name_letters_indicator = func_select(name_letters_indicator, 8, 0);
}
//Main Screen
if (screen == 1){
else if (screen == 1){
sel = func_select(sel, bottom_bar_size-1, 0);
}
@@ -335,29 +382,32 @@ void check_bt(){
}
}
//========================================================================
//============================= Button Right =============================
//========================================================================
if (digitalRead(bt_right) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1;
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis();
//Name Screen
if (screen == 0){
name_letters_indicator = func_select(name_letters_indicator, 8, 1);
}
//Main Screen
if (screen == 1){
else if (screen == 1){
sel = func_select(sel, bottom_bar_size - 1, 1);
}
//Status Screen
else if (screen == 2){
screen = 1;
}
}
}
}
uint8_t func_select(uint8_t var, uint8_t max, uint8_t dir){
uint8_t func_select(uint8_t var, uint8_t max, uint8_t dir){ //dir 0: negative, 1:positive
if(dir > 0){
if(var < max){var += 1;}
else{var = 0;}
@@ -437,7 +487,7 @@ void check_pet(){
if (pet.health <= 50){
health_fail = true;
}
if (pet.happy <= 70){
if (pet.happy <= 50){
happy_fail = true;
}
@@ -447,15 +497,15 @@ void check_pet(){
void evolve(){
switch (pet_lvl){
case 0:
pet_species = 1;
pet_species = 1;
break;
case 1:
case 1:
pet_species = 2; //evolve to Bunny
if (happy_fail == false && food_check[0] == 0 && food_check[1] == 0 && food_check[2] == 0){ //evolve to T-Rex
pet_species = 4;
if (happy_fail == false && health_fail == false && food_check[0] == 0 && food_check[1] == 0 && food_check[2] == 0){ //(only eats chicken or candy) - food_check: 0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
pet_species = 4; //evolve to T-Rex
}
else if (health_fail == false && food_check[0] > 0 && food_check[1] > 0 && food_check[2] > 0 && food_check[3] == 0){ //evolve to Dino
pet_species = 3;
else if (health_fail == false && food_check[0] > 0 && food_check[1] > 0 && food_check[2] > 0 && food_check[3] == 0){ //(eats every vegetable. can eat candy too) - food_check: 0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
pet_species = 3; //evolve to Dino
}
break;
}
@@ -470,6 +520,10 @@ void clamp_pet(){
if(pet.sleep > 200){pet.sleep = 200;}
}
//========================================================================
//================================ Misc ==================================
//========================================================================
void ani_check(){
if (pet_mood == 0) {
pet_ani = ani_tick % 2;
@@ -485,6 +539,32 @@ void ani_check(){
}
}
// Generates the bottom bar
void bottom_bar_gen(){
bottom_bar_sel_size = 127 / bottom_bar_size;
for(uint8_t i = 0; i < bottom_bar_size; i++){
bottom_bar_pos[i] = (i* bottom_bar_sel_size) + ((bottom_bar_sel_size - 8)/2);
}
}
// Make the age string on the status screen
void age_gen(){
if (pet_alive == true){
if (act_time[3]!=0){
pet_age = String(act_time[3]) + "D";
}
else if (act_time[2]!=0){
pet_age = String(act_time[2]) + "H";
}
else if (act_time[1]!=0){
pet_age = String(act_time[1]) + "m";
}
else{
pet_age = "0m";
}
}
}
void resize(const uint8_t* bmp, uint16_t w, uint16_t h, int16_t x0, int16_t y0, uint8_t scale) {
if (!scale) return; // avoid divide-by-zero
@@ -541,6 +621,30 @@ void draw(){
oled.setCursorXY(0, 0);
oled.line(0, 10, 127, 10);
//========================================================================
//===============================Name Screen==============================
//========================================================================
if (screen == 0){
draw_sex(1, 16);
oled.setCursor(16,3);
oled.setScale(2);
draw_letter_scroll();
oled.setScale(1);
if (name_letters_indicator == 8){
oled.invertText(true);
}
oled.setCursorXY(91,55);
oled.print("Start!");
oled.invertText(false);
oled.rect(89,53,127,63,2);
if (name_letters_indicator == 8){
oled.line(90,54,90,63);
oled.line(90,54,127,54);
}
}
//========================================================================
//===============================Main Screen==============================
//========================================================================
@@ -562,7 +666,7 @@ void draw(){
}
if (pet_poop == true){
resize(icon_poop, 8, 8, 112, 32, 2); //bmp, w, h, x0, y0, scale
resize(icon_poop, 8, 8, 104, 32, 2); //bmp, w, h, x0, y0, scale
}
//draw bottom bar
@@ -580,6 +684,10 @@ void draw(){
oled.drawBitmap(bottom_bar_pos[i], 55, bottom_bar_icons[i-1], 8, 8, 0, invert_sprite);
}
}
if (evolve_ani == true){
draw_explosion();
}
}
//========================================================================
@@ -587,14 +695,15 @@ void draw(){
//========================================================================
else if(screen == 2){
//Draw Infos
if (pet_sex == 0) {oled.drawBitmap(1, 16, icon_fem, 8, 8);}
else {oled.drawBitmap(1, 16, icon_male, 8, 8);}
draw_sex(1, 16);
oled.setCursorXY(12, 16);
oled.print(pet_all_species[pet_species]);
oled.print(pet_name);
oled.setCursorXY(1, 28);
oled.print(pet_all_species[pet_species]);
oled.setCursorXY(1, 40);
oled.print("Age: ");
oled.print(pet_age);
oled.setCursorXY(1, 40);
oled.setCursorXY(1, 52);
oled.print("Lvl: ");
if(pet_lvl < 2){oled.print(pet_lvl);}
else{oled.print("MAX");}
@@ -623,21 +732,14 @@ void draw(){
oled.rect(66 + (i*13), 18 + (34 - (34 * pet.happy)/100), 70 + (i*13), 52);
}
}
//Draw Back Button
oled.rect(0, 55, 6, 63);
oled.setCursor(1, 7);
oled.invertText(true);
oled.print("<");
oled.invertText(false);
}
if (evolve_ani == true){
draw_explosion();
}
oled.update();
}
void draw_sex(uint8_t x, uint8_t y){
if (pet_sex == 0) {oled.drawBitmap(x, y, icon_fem, 8, 8);}
else {oled.drawBitmap(x, y, icon_male, 8, 8);}
}
//Solving the sprite puzzle
void draw_pet(){
uint8_t animation_eyes;
@@ -713,9 +815,22 @@ void draw_explosion(){
}
}
void draw_letter_scroll(){
for(uint8_t i=0; i<8; i++){
if (i == name_letters_indicator){
oled.invertText(true);
}
oled.print(name_letters_array[i]);
oled.invertText(false);
}
}
void loop() {
check_bt();
time_check();
if (timer_rolling == true){
time_check();
}
if(millis() > draw_tick + draw_tick_max){ //controls the screens refresh to save some battery
draw_tick = millis();